[MNG-8447] Lossy ProblemCollector (#1994)

Historically (from Maven3) "problem collection" (in various scenarios, like building effective settings and toolchains, to building models) were done by passing around `List<Problem>` structure. This proved quite ineffective, as in case of (really) huge projects with quite big count of problems reported choked Maven (but also produced unusable output). We are aware of projects producing 3 million warnings! Dumping all of them onto console/log makes really no sense.

This PR changes Maven that by default it reports "problems" (settings building, toolchains building, or model building) as one liner warnings: how much and where were problems collected. This produces much more less overwhelming output than happens on master. User can control "build errors" using `-e`, so `mvn -e` will dump errors on screen/log.

The new `org.apache.maven.api.services.ProblemCollector<P>` class, that is somewhat "drop in" replacement for `List`, but with huge difference: it is "lossy", in a way, it maintains counters precisely, but actual problem instances are simply dropped after threshold `maven.builder.maxProblems` is surpassed (default 100). See `org.apache.maven.api.Constants#MAVEN_BUILDER_MAX_PROBLEMS`.

Rules:
* on problem addition counters are always updated
* if below threshold, store the problem (and will be reported later to the user)
* if above threshold, try to drop one problem with severity lower than currently reported problem, if succeeded, store the current problem, otherwise drop it

---

https://issues.apache.org/jira/browse/MNG-8447
This commit is contained in:
Tamas Cservenak 2024-12-19 15:50:21 +01:00 committed by GitHub
parent 5599934016
commit 0222aff6c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 651 additions and 554 deletions

View File

@ -18,10 +18,6 @@
*/
package org.apache.maven.api.services;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.apache.maven.api.annotations.Experimental;
/**
@ -32,14 +28,14 @@
@Experimental
public abstract class MavenBuilderException extends MavenException {
private final List<BuilderProblem> problems;
private final ProblemCollector<BuilderProblem> problems;
public MavenBuilderException(String message, Throwable cause) {
super(message, cause);
problems = List.of();
problems = ProblemCollector.empty();
}
public MavenBuilderException(String message, List<BuilderProblem> problems) {
public MavenBuilderException(String message, ProblemCollector<BuilderProblem> problems) {
super(buildMessage(message, problems), null);
this.problems = problems;
}
@ -49,20 +45,16 @@ public MavenBuilderException(String message, List<BuilderProblem> problems) {
* and then a list is built. These exceptions are usually thrown in "fatal" cases (and usually prevent Maven
* from starting), and these exceptions may end up very early on output.
*/
protected static String buildMessage(String message, List<BuilderProblem> problems) {
protected static String buildMessage(String message, ProblemCollector<BuilderProblem> problems) {
StringBuilder msg = new StringBuilder(message);
ArrayList<BuilderProblem> sorted = new ArrayList<>(problems);
sorted.sort(Comparator.comparing(BuilderProblem::getSeverity));
for (BuilderProblem problem : sorted) {
msg.append("\n * ")
.append(problem.getSeverity().name())
.append(": ")
.append(problem.getMessage());
}
problems.problems().forEach(problem -> msg.append("\n * ")
.append(problem.getSeverity().name())
.append(": ")
.append(problem.getMessage()));
return msg.toString();
}
public List<BuilderProblem> getProblems() {
public ProblemCollector<BuilderProblem> getProblemCollector() {
return problems;
}
}

View File

@ -19,8 +19,6 @@
package org.apache.maven.api.services;
import java.io.Serial;
import java.util.Collections;
import java.util.List;
import org.apache.maven.api.annotations.Experimental;
@ -82,10 +80,10 @@ public String getModelId() {
*
* @return The problems that caused this exception, never {@code null}.
*/
public List<ModelProblem> getProblems() {
public ProblemCollector<ModelProblem> getProblemCollector() {
if (result == null) {
return Collections.emptyList();
return ProblemCollector.empty();
}
return Collections.unmodifiableList(result.getProblems());
return result.getProblemCollector();
}
}

View File

@ -91,12 +91,12 @@ public interface ModelBuilderResult {
List<Profile> getActiveExternalProfiles();
/**
* Gets the problems that were encountered during the project building.
* Gets the problem collector that collected problems encountered during the project building.
*
* @return the problems that were encountered during the project building, can be empty but never {@code null}
* @return the problem collector that collected problems encountered during the project building
*/
@Nonnull
List<ModelProblem> getProblems();
ProblemCollector<ModelProblem> getProblemCollector();
/**
* Gets the children of this result.

View File

@ -18,8 +18,6 @@
*/
package org.apache.maven.api.services;
import java.util.List;
import org.apache.maven.api.model.InputLocation;
import org.apache.maven.api.model.Model;
@ -33,15 +31,15 @@
*/
public interface ModelProblemCollector {
/**
* The collected problems.
* @return a list of model problems encountered, never {@code null}
*/
List<ModelProblem> getProblems();
ProblemCollector<ModelProblem> getProblemCollector();
boolean hasErrors();
default boolean hasErrors() {
return getProblemCollector().hasErrorProblems();
}
boolean hasFatalErrors();
default boolean hasFatalErrors() {
return getProblemCollector().hasFatalProblems();
}
default void add(BuilderProblem.Severity severity, ModelProblem.Version version, String message) {
add(severity, version, message, null, null);
@ -64,7 +62,9 @@ void add(
InputLocation location,
Exception exception);
void add(ModelProblem problem);
default void add(ModelProblem problem) {
getProblemCollector().reportProblem(problem);
}
ModelBuilderException newModelBuilderException();

View File

@ -0,0 +1,261 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.services;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Stream;
import org.apache.maven.api.Constants;
import org.apache.maven.api.ProtoSession;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
import static java.util.Objects.requireNonNull;
/**
* Collects problems that were encountered during project building.
*
* @param <P> The type of the problem.
* @since 4.0.0
*/
@Experimental
public interface ProblemCollector<P extends BuilderProblem> {
/**
* Returns {@code true} if there is at least one problem collected with severity equal or more severe than
* {@link org.apache.maven.api.services.BuilderProblem.Severity#WARNING}. This check is logically equivalent
* to "is there any problem reported?", given warning is the lowest severity.
*/
default boolean hasWarningProblems() {
return hasProblemsFor(BuilderProblem.Severity.WARNING);
}
/**
* Returns {@code true} if there is at least one problem collected with severity equal or more severe than
* {@link org.apache.maven.api.services.BuilderProblem.Severity#ERROR}.
*/
default boolean hasErrorProblems() {
return hasProblemsFor(BuilderProblem.Severity.ERROR);
}
/**
* Returns {@code true} if there is at least one problem collected with severity equal or more severe than
* {@link org.apache.maven.api.services.BuilderProblem.Severity#FATAL}.
*/
default boolean hasFatalProblems() {
return hasProblemsFor(BuilderProblem.Severity.FATAL);
}
/**
* Returns {@code true} if there is at least one problem collected with severity equal or more severe than
* passed in severity.
*/
default boolean hasProblemsFor(BuilderProblem.Severity severity) {
requireNonNull(severity, "severity");
for (BuilderProblem.Severity s : BuilderProblem.Severity.values()) {
if (s.ordinal() <= severity.ordinal() && problemsReportedFor(s) > 0) {
return true;
}
}
return false;
}
/**
* Returns total count of problems reported.
*/
default int totalProblemsReported() {
return problemsReportedFor(BuilderProblem.Severity.values());
}
/**
* Returns count of problems reported for given severities.
*/
int problemsReportedFor(BuilderProblem.Severity... severities);
/**
* Returns {@code true} if reported problem count exceeded allowed count, and issues were lost. When this
* method returns {@code true}, it means that element count of stream returned by method {@link #problems()}
* and the counter returned by {@link #totalProblemsReported()} are not equal (latter is bigger than former).
*/
boolean problemsOverflow();
/**
* Reports a problem: always maintains the counters, but whether problem is preserved in memory, depends on
* implementation and its configuration.
*
* @return {@code true} if passed problem is preserved by this call.
*/
boolean reportProblem(P problem);
/**
* Returns all reported and preserved problems ordered by severity in decreasing order. Note: counters and
* element count in this stream does not have to be equal.
*/
@Nonnull
default Stream<P> problems() {
Stream<P> result = Stream.empty();
for (BuilderProblem.Severity severity : BuilderProblem.Severity.values()) {
result = Stream.concat(result, problems(severity));
}
return result;
}
/**
* Returns all reported and preserved problems for given severity. Note: counters and element count in this
* stream does not have to be equal.
*/
@Nonnull
Stream<P> problems(BuilderProblem.Severity severity);
/**
* Creates "empty" problem collector.
*/
@Nonnull
static <P extends BuilderProblem> ProblemCollector<P> empty() {
return new ProblemCollector<>() {
@Override
public boolean problemsOverflow() {
return false;
}
@Override
public int problemsReportedFor(BuilderProblem.Severity... severities) {
return 0;
}
@Override
public boolean reportProblem(P problem) {
throw new IllegalStateException("empty problem collector");
}
@Override
public Stream<P> problems(BuilderProblem.Severity severity) {
return Stream.empty();
}
};
}
/**
* Creates new instance of problem collector.
*/
@Nonnull
static <P extends BuilderProblem> ProblemCollector<P> create(@Nullable ProtoSession protoSession) {
if (protoSession != null
&& protoSession.getUserProperties().containsKey(Constants.MAVEN_BUILDER_MAX_PROBLEMS)) {
return new Impl<>(
Integer.parseInt(protoSession.getUserProperties().get(Constants.MAVEN_BUILDER_MAX_PROBLEMS)));
} else {
return create(100);
}
}
/**
* Creates new instance of problem collector. Visible for testing only.
*/
@Nonnull
static <P extends BuilderProblem> ProblemCollector<P> create(int maxCountLimit) {
return new Impl<>(maxCountLimit);
}
class Impl<P extends BuilderProblem> implements ProblemCollector<P> {
private final int maxCountLimit;
private final AtomicInteger totalCount;
private final ConcurrentMap<BuilderProblem.Severity, LongAdder> counters;
private final ConcurrentMap<BuilderProblem.Severity, List<P>> problems;
private static final List<BuilderProblem.Severity> REVERSED_ORDER = Arrays.stream(
BuilderProblem.Severity.values())
.sorted(Comparator.reverseOrder())
.toList();
private Impl(int maxCountLimit) {
if (maxCountLimit < 0) {
throw new IllegalArgumentException("maxCountLimit must be non-negative");
}
this.maxCountLimit = maxCountLimit;
this.totalCount = new AtomicInteger();
this.counters = new ConcurrentHashMap<>();
this.problems = new ConcurrentHashMap<>();
}
@Override
public int problemsReportedFor(BuilderProblem.Severity... severity) {
int result = 0;
for (BuilderProblem.Severity s : severity) {
result += getCounter(s).intValue();
}
return result;
}
@Override
public boolean problemsOverflow() {
return totalCount.get() > maxCountLimit;
}
@Override
public boolean reportProblem(P problem) {
requireNonNull(problem, "problem");
int currentCount = totalCount.incrementAndGet();
getCounter(problem.getSeverity()).increment();
if (currentCount <= maxCountLimit || dropProblemWithLowerSeverity(problem.getSeverity())) {
getProblems(problem.getSeverity()).add(problem);
return true;
}
return false;
}
@Override
public Stream<P> problems(BuilderProblem.Severity severity) {
requireNonNull(severity, "severity");
return getProblems(severity).stream();
}
private LongAdder getCounter(BuilderProblem.Severity severity) {
return counters.computeIfAbsent(severity, k -> new LongAdder());
}
private List<P> getProblems(BuilderProblem.Severity severity) {
return problems.computeIfAbsent(severity, k -> new CopyOnWriteArrayList<>());
}
private boolean dropProblemWithLowerSeverity(BuilderProblem.Severity severity) {
for (BuilderProblem.Severity s : REVERSED_ORDER) {
if (s.ordinal() > severity.ordinal()) {
List<P> problems = getProblems(s);
while (!problems.isEmpty()) {
try {
return problems.remove(0) != null;
} catch (IndexOutOfBoundsException e) {
// empty, continue
}
}
}
}
return false;
}
}
}

View File

@ -19,7 +19,6 @@
package org.apache.maven.api.services;
import java.nio.file.Path;
import java.util.List;
import org.apache.maven.api.Service;
import org.apache.maven.api.Session;
@ -108,7 +107,7 @@ default SettingsBuilderResult build(
* @return The list of problems that were encountered, must not be {@code null}.
*/
@Nonnull
default List<BuilderProblem> validate(@Nonnull Settings settings) {
default ProblemCollector<BuilderProblem> validate(@Nonnull Settings settings) {
return validate(settings, false);
}
@ -120,7 +119,7 @@ default List<BuilderProblem> validate(@Nonnull Settings settings) {
* @return The list of problems that were encountered, must not be {@code null}.
*/
@Nonnull
List<BuilderProblem> validate(@Nonnull Settings settings, boolean isProjectSettings);
ProblemCollector<BuilderProblem> validate(@Nonnull Settings settings, boolean isProjectSettings);
/**
* Convert a model profile to a settings profile.

View File

@ -19,7 +19,6 @@
package org.apache.maven.api.services;
import java.io.Serial;
import java.util.List;
import org.apache.maven.api.annotations.Experimental;
@ -42,7 +41,7 @@ public SettingsBuilderException(String message, Exception e) {
super(message, e);
}
public SettingsBuilderException(String message, List<BuilderProblem> problems) {
public SettingsBuilderException(String message, ProblemCollector<BuilderProblem> problems) {
super(message, problems);
}
}

View File

@ -18,8 +18,6 @@
*/
package org.apache.maven.api.services;
import java.util.List;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.settings.Settings;
@ -47,5 +45,5 @@ public interface SettingsBuilderResult {
* @return the problems that were encountered during the settings building, can be empty but never {@code null}
*/
@Nonnull
List<BuilderProblem> getProblems();
ProblemCollector<BuilderProblem> getProblems();
}

View File

@ -19,7 +19,6 @@
package org.apache.maven.api.services;
import java.io.Serial;
import java.util.List;
import org.apache.maven.api.annotations.Experimental;
@ -42,7 +41,7 @@ public ToolchainsBuilderException(String message, Exception e) {
super(message, e);
}
public ToolchainsBuilderException(String message, List<BuilderProblem> problems) {
public ToolchainsBuilderException(String message, ProblemCollector<BuilderProblem> problems) {
super(message, problems);
}
}

View File

@ -18,8 +18,6 @@
*/
package org.apache.maven.api.services;
import java.util.List;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.toolchain.PersistedToolchains;
@ -46,5 +44,5 @@ public interface ToolchainsBuilderResult {
* @return the problems that were encountered during the settings building, can be empty but never {@code null}
*/
@Nonnull
List<BuilderProblem> getProblems();
ProblemCollector<BuilderProblem> getProblems();
}

View File

@ -95,9 +95,11 @@ public SettingsBuildingResult build(SettingsBuildingRequest request) throws Sett
.build());
return new DefaultSettingsBuildingResult(
new Settings(result.getEffectiveSettings()), convert(result.getProblems()));
new Settings(result.getEffectiveSettings()),
convert(result.getProblems().problems().toList()));
} catch (SettingsBuilderException e) {
throw new SettingsBuildingException(convert(e.getProblems()));
throw new SettingsBuildingException(
convert(e.getProblemCollector().problems().toList()));
}
}

View File

@ -22,10 +22,10 @@
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ProblemCollector;
import org.apache.maven.api.services.SettingsBuilder;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.building.SettingsProblem.Severity;
@ -56,8 +56,8 @@ public void validate(Settings settings, SettingsProblemCollector problems) {
@Override
public void validate(Settings settings, boolean isProjectSettings, SettingsProblemCollector problems) {
List<BuilderProblem> list = settingsBuilder.validate(settings.getDelegate(), isProjectSettings);
for (BuilderProblem problem : list) {
ProblemCollector<BuilderProblem> list = settingsBuilder.validate(settings.getDelegate(), isProjectSettings);
for (BuilderProblem problem : list.problems().toList()) {
addViolation(problems, Severity.valueOf(problem.getSeverity().name()), problem.getMessage());
}
}

View File

@ -99,9 +99,10 @@ void testValidateMirror() throws Exception {
SimpleProblemCollector problems = new SimpleProblemCollector();
validator.validate(settings, problems);
assertEquals(4, problems.messages.size());
assertContains(problems.messages.get(0), "'mirrors.mirror.id' must not be 'local'");
assertContains(problems.messages.get(1), "'mirrors.mirror.url' for local is missing");
assertContains(problems.messages.get(2), "'mirrors.mirror.mirrorOf' for local is missing");
// errors are now by severity descending
assertContains(problems.messages.get(0), "'mirrors.mirror.url' for local is missing");
assertContains(problems.messages.get(1), "'mirrors.mirror.mirrorOf' for local is missing");
assertContains(problems.messages.get(2), "'mirrors.mirror.id' must not be 'local'");
assertContains(problems.messages.get(3), "'mirrors.mirror.id' must not contain any of these characters");
}
@ -121,11 +122,12 @@ void testValidateRepository() throws Exception {
SimpleProblemCollector problems = new SimpleProblemCollector();
validator.validate(settings, problems);
assertEquals(3, problems.messages.size());
// errors are now by severity descending
assertContains(
problems.messages.get(0), "'profiles.profile[default].repositories.repository.id' must not be 'local'");
assertContains(
problems.messages.get(1),
problems.messages.get(0),
"'profiles.profile[default].repositories.repository.url' for local is missing");
assertContains(
problems.messages.get(1), "'profiles.profile[default].repositories.repository.id' must not be 'local'");
assertContains(
problems.messages.get(2),
"'profiles.profile[default].repositories.repository.id' must not contain any of these characters");

View File

@ -92,9 +92,11 @@ public ToolchainsBuildingResult build(ToolchainsBuildingRequest request) throws
.build());
return new DefaultToolchainsBuildingResult(
new PersistedToolchains(result.getEffectiveToolchains()), convert(result.getProblems()));
new PersistedToolchains(result.getEffectiveToolchains()),
convert(result.getProblems().problems().toList()));
} catch (ToolchainsBuilderException e) {
throw new ToolchainsBuildingException(convert(e.getProblems()));
throw new ToolchainsBuildingException(
convert(e.getProblemCollector().problems().toList()));
}
}

View File

@ -597,13 +597,16 @@ protected void settings(C context, boolean emitSettingsWarnings, SettingsBuilder
context.interactive = mayDisableInteractiveMode(context, context.effectiveSettings.isInteractiveMode());
context.localRepositoryPath = localRepositoryPath(context);
if (emitSettingsWarnings && !settingsResult.getProblems().isEmpty()) {
if (emitSettingsWarnings && settingsResult.getProblems().hasWarningProblems()) {
int totalProblems = settingsResult.getProblems().totalProblemsReported();
context.logger.info("");
context.logger.info(
"Some problems were encountered while building the effective settings (use -X to see details)");
context.logger.info(String.format(
"%s %s encountered while building the effective settings (use -e to see details)",
totalProblems, (totalProblems == 1) ? "problem was" : "problems were"));
if (context.invokerRequest.options().verbose().orElse(false)) {
for (BuilderProblem problem : settingsResult.getProblems()) {
if (context.invokerRequest.options().showErrors().orElse(false)) {
for (BuilderProblem problem :
settingsResult.getProblems().problems().toList()) {
context.logger.warn(problem.getMessage() + " @ " + problem.getLocation());
}
}

View File

@ -205,15 +205,21 @@ protected void toolchains(MavenContext context, MavenExecutionRequest request) t
new org.apache.maven.toolchain.model.PersistedToolchains(
toolchainsResult.getEffectiveToolchains()));
if (!toolchainsResult.getProblems().isEmpty()) {
context.logger.warn("");
context.logger.warn("Some problems were encountered while building the effective toolchains");
if (toolchainsResult.getProblems().hasWarningProblems()) {
int totalProblems = toolchainsResult.getProblems().totalProblemsReported();
context.logger.info("");
context.logger.info(String.format(
"%s %s encountered while building the effective toolchains (use -e to see details)",
totalProblems, (totalProblems == 1) ? "problem was" : "problems were"));
for (BuilderProblem problem : toolchainsResult.getProblems()) {
context.logger.warn(problem.getMessage() + " @ " + problem.getLocation());
if (context.invokerRequest.options().showErrors().orElse(false)) {
for (BuilderProblem problem :
toolchainsResult.getProblems().problems().toList()) {
context.logger.warn(problem.getMessage() + " @ " + problem.getLocation());
}
}
context.logger.warn("");
context.logger.info("");
}
}

View File

@ -66,6 +66,7 @@
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.ModelSource;
import org.apache.maven.api.services.ModelTransformer;
import org.apache.maven.api.services.ProblemCollector;
import org.apache.maven.api.services.Source;
import org.apache.maven.api.services.model.LifecycleBindingsInjector;
import org.apache.maven.artifact.Artifact;
@ -340,7 +341,7 @@ ProjectBuildingResult build(Path pomFile, ModelSource modelSource) throws Projec
try {
MavenProject project = request.getProject();
List<ModelProblem> modelProblems = null;
ProblemCollector<ModelProblem> problemCollector = null;
Throwable error = null;
if (project == null) {
@ -378,7 +379,7 @@ ProjectBuildingResult build(Path pomFile, ModelSource modelSource) throws Projec
error = e;
}
modelProblems = result.getProblems();
problemCollector = result.getProblemCollector();
initProject(project, result);
}
@ -391,7 +392,7 @@ ProjectBuildingResult build(Path pomFile, ModelSource modelSource) throws Projec
}
ProjectBuildingResult result =
new DefaultProjectBuildingResult(project, convert(modelProblems), resolutionResult);
new DefaultProjectBuildingResult(project, convert(problemCollector), resolutionResult);
if (error != null) {
ProjectBuildingException e = new ProjectBuildingException(List.of(result));
@ -498,18 +499,14 @@ private List<ProjectBuildingResult> build(File pomFile, boolean recursive) {
} catch (ModelBuilderException e) {
result = e.getResult();
if (result == null || result.getEffectiveModel() == null) {
return List.of(new DefaultProjectBuildingResult(e.getModelId(), pomFile, convert(e.getProblems())));
return List.of(new DefaultProjectBuildingResult(
e.getModelId(), pomFile, convert(e.getProblemCollector())));
}
}
List<ProjectBuildingResult> results = new ArrayList<>();
List<ModelBuilderResult> allModels = results(result).toList();
for (ModelBuilderResult r : allModels) {
List<ModelProblem> problems = new ArrayList<>(r.getProblems());
results(r)
.filter(c -> c != r)
.flatMap(c -> c.getProblems().stream())
.forEach(problems::remove);
if (r.getEffectiveModel() != null) {
File pom = r.getSource().getPath().toFile();
MavenProject project =
@ -529,9 +526,10 @@ private List<ProjectBuildingResult> build(File pomFile, boolean recursive) {
if (request.isResolveDependencies()) {
resolutionResult = resolveDependencies(project);
}
results.add(new DefaultProjectBuildingResult(project, convert(problems), resolutionResult));
results.add(new DefaultProjectBuildingResult(
project, convert(r.getProblemCollector()), resolutionResult));
} else {
results.add(new DefaultProjectBuildingResult(null, convert(problems), null));
results.add(new DefaultProjectBuildingResult(null, convert(r.getProblemCollector()), null));
}
}
return results;
@ -541,11 +539,34 @@ private Stream<ModelBuilderResult> results(ModelBuilderResult result) {
return Stream.concat(result.getChildren().stream().flatMap(this::results), Stream.of(result));
}
private List<org.apache.maven.model.building.ModelProblem> convert(List<ModelProblem> problems) {
if (problems == null) {
private List<org.apache.maven.model.building.ModelProblem> convert(
ProblemCollector<ModelProblem> problemCollector) {
if (problemCollector == null) {
return null;
}
return problems.stream().map(p -> convert(p)).toList();
ArrayList<org.apache.maven.model.building.ModelProblem> problems = new ArrayList<>();
problemCollector.problems().map(BuildSession::convert).forEach(problems::add);
if (problemCollector.problemsOverflow()) {
problems.add(
0,
new DefaultModelProblem(
"Too many model problems reported (listed problems are just a subset of reported problems)",
org.apache.maven.model.building.ModelProblem.Severity.WARNING,
null,
null,
-1,
-1,
null,
null));
return new ArrayList<>(problems) {
@Override
public int size() {
return problemCollector.totalProblemsReported();
}
};
} else {
return problems;
}
}
private static org.apache.maven.model.building.ModelProblem convert(ModelProblem p) {

View File

@ -62,27 +62,33 @@ public List<MavenProject> selectProjects(List<File> files, MavenExecutionRequest
List<MavenProject> projects = new ArrayList<>(results.size());
boolean problems = false;
long totalProblemsCount = 0;
for (ProjectBuildingResult result : results) {
projects.add(result.getProject());
if (!result.getProblems().isEmpty() && LOGGER.isWarnEnabled()) {
int problemsCount = result.getProblems().size();
totalProblemsCount += problemsCount;
if (problemsCount != 0 && LOGGER.isWarnEnabled()) {
LOGGER.warn("");
LOGGER.warn(
"Some problems were encountered while building the effective model for '{}'",
"{} {} encountered while building the effective model for '{}' (use -e to see details)",
problemsCount,
(problemsCount == 1) ? "problem was" : "problems were",
result.getProject().getId());
for (ModelProblem problem : result.getProblems()) {
String loc = ModelProblemUtils.formatLocation(problem, result.getProjectId());
LOGGER.warn("{}{}", problem.getMessage(), ((loc != null && !loc.isEmpty()) ? " @ " + loc : ""));
if (request.isShowErrors()) { // this means -e or -X (as -X enables -e as well)
for (ModelProblem problem : result.getProblems()) {
String loc = ModelProblemUtils.formatLocation(problem, result.getProjectId());
LOGGER.warn("{}{}", problem.getMessage(), ((loc != null && !loc.isEmpty()) ? " @ " + loc : ""));
}
}
problems = true;
}
}
if (problems) {
if (totalProblemsCount > 0) {
LOGGER.warn("");
LOGGER.warn("Total model problems reported: {}", totalProblemsCount);
LOGGER.warn("");
LOGGER.warn("It is highly recommended to fix these problems"
+ " because they threaten the stability of your build.");

View File

@ -26,7 +26,6 @@
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@ -39,6 +38,7 @@
import org.apache.maven.api.di.Named;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.Interpolator;
import org.apache.maven.api.services.ProblemCollector;
import org.apache.maven.api.services.SettingsBuilder;
import org.apache.maven.api.services.SettingsBuilderException;
import org.apache.maven.api.services.SettingsBuilderRequest;
@ -97,7 +97,7 @@ public DefaultSettingsBuilder(
@Override
public SettingsBuilderResult build(SettingsBuilderRequest request) throws SettingsBuilderException {
List<BuilderProblem> problems = new ArrayList<>();
ProblemCollector<BuilderProblem> problems = ProblemCollector.create(request.getSession());
Source installationSource = request.getInstallationSettingsSource().orElse(null);
Settings installation = readSettings(installationSource, false, request, problems);
@ -139,30 +139,18 @@ public SettingsBuilderResult build(SettingsBuilderRequest request) throws Settin
}
}
if (hasErrors(problems)) {
if (problems.hasErrorProblems()) {
throw new SettingsBuilderException("Error building settings", problems);
}
return new DefaultSettingsBuilderResult(effective, problems);
}
private boolean hasErrors(List<BuilderProblem> problems) {
if (problems != null) {
for (BuilderProblem problem : problems) {
if (BuilderProblem.Severity.ERROR.compareTo(problem.getSeverity()) >= 0) {
return true;
}
}
}
return false;
}
private Settings readSettings(
Source settingsSource,
boolean isProjectSettings,
SettingsBuilderRequest request,
List<BuilderProblem> problems) {
ProblemCollector<BuilderProblem> problems) {
if (settingsSource == null) {
return Settings.newInstance();
}
@ -184,7 +172,7 @@ private Settings readSettings(
.strict(false)
.build());
Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
problems.add(new DefaultBuilderProblem(
problems.reportProblem(new DefaultBuilderProblem(
settingsSource.getLocation(),
loc != null ? loc.getLineNumber() : -1,
loc != null ? loc.getColumnNumber() : -1,
@ -195,7 +183,7 @@ private Settings readSettings(
}
} catch (XmlReaderException e) {
Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
problems.add(new DefaultBuilderProblem(
problems.reportProblem(new DefaultBuilderProblem(
settingsSource.getLocation(),
loc != null ? loc.getLineNumber() : -1,
loc != null ? loc.getColumnNumber() : -1,
@ -204,7 +192,7 @@ private Settings readSettings(
BuilderProblem.Severity.FATAL));
return Settings.newInstance();
} catch (IOException e) {
problems.add(new DefaultBuilderProblem(
problems.reportProblem(new DefaultBuilderProblem(
settingsSource.getLocation(),
-1,
-1,
@ -242,7 +230,8 @@ private Settings readSettings(
return settings;
}
private Settings interpolate(Settings settings, SettingsBuilderRequest request, List<BuilderProblem> problems) {
private Settings interpolate(
Settings settings, SettingsBuilderRequest request, ProblemCollector<BuilderProblem> problems) {
Function<String, String> src;
if (request.getInterpolationSource().isPresent()) {
src = request.getInterpolationSource().get();
@ -269,7 +258,10 @@ protected Activation.Builder transformActivation_Condition(
}
private Settings decrypt(
Source settingsSource, Settings settings, SettingsBuilderRequest request, List<BuilderProblem> problems) {
Source settingsSource,
Settings settings,
SettingsBuilderRequest request,
ProblemCollector<BuilderProblem> problems) {
if (dispatchers.isEmpty()) {
return settings;
}
@ -284,7 +276,7 @@ private Settings decrypt(
try {
return secDispatcher.decrypt(str);
} catch (Exception e) {
problems.add(new DefaultBuilderProblem(
problems.reportProblem(new DefaultBuilderProblem(
settingsSource.getLocation(),
-1,
-1,
@ -297,7 +289,7 @@ private Settings decrypt(
};
Settings result = new SettingsTransformer(decryptFunction).visit(settings);
if (preMaven4Passwords.get() > 0) {
problems.add(new DefaultBuilderProblem(
problems.reportProblem(new DefaultBuilderProblem(
settingsSource.getLocation(),
-1,
-1,
@ -323,8 +315,9 @@ private Path getSecuritySettings(ProtoSession session) {
}
@Override
public List<BuilderProblem> validate(Settings settings, boolean isProjectSettings) {
ArrayList<BuilderProblem> problems = new ArrayList<>();
public ProblemCollector<BuilderProblem> validate(Settings settings, boolean isProjectSettings) {
// TODO: any way to get ProtoSession here?
ProblemCollector<BuilderProblem> problems = ProblemCollector.create(null);
settingsValidator.validate(settings, isProjectSettings, problems);
return problems;
}
@ -347,11 +340,11 @@ static class DefaultSettingsBuilderResult implements SettingsBuilderResult {
private final Settings effectiveSettings;
private final List<BuilderProblem> problems;
private final ProblemCollector<BuilderProblem> problems;
DefaultSettingsBuilderResult(Settings effectiveSettings, List<BuilderProblem> problems) {
DefaultSettingsBuilderResult(Settings effectiveSettings, ProblemCollector<BuilderProblem> problems) {
this.effectiveSettings = effectiveSettings;
this.problems = (problems != null) ? problems : new ArrayList<>();
this.problems = (problems != null) ? problems : ProblemCollector.empty();
}
@Override
@ -360,7 +353,7 @@ public Settings getEffectiveSettings() {
}
@Override
public List<BuilderProblem> getProblems() {
public ProblemCollector<BuilderProblem> getProblems() {
return problems;
}
}

View File

@ -24,6 +24,7 @@
import java.util.regex.Pattern;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ProblemCollector;
import org.apache.maven.api.settings.Mirror;
import org.apache.maven.api.settings.Profile;
import org.apache.maven.api.settings.Proxy;
@ -41,7 +42,7 @@ public class DefaultSettingsValidator {
private static final String ILLEGAL_REPO_ID_CHARS = "\\/:\"<>|?*"; // ILLEGAL_FS_CHARS
@SuppressWarnings("checkstyle:MethodLength")
public void validate(Settings settings, boolean isProjectSettings, List<BuilderProblem> problems) {
public void validate(Settings settings, boolean isProjectSettings, ProblemCollector<BuilderProblem> problems) {
if (isProjectSettings) {
String msgS = "is not supported on project settings.";
String msgP = "are not supported on project settings.";
@ -208,7 +209,8 @@ public void validate(Settings settings, boolean isProjectSettings, List<BuilderP
}
}
private void validateRepositories(List<BuilderProblem> problems, List<Repository> repositories, String prefix) {
private void validateRepositories(
ProblemCollector<BuilderProblem> problems, List<Repository> repositories, String prefix) {
Set<String> repoIds = new HashSet<>();
for (Repository repository : repositories) {
@ -268,7 +270,7 @@ private void validateRepositories(List<BuilderProblem> problems, List<Repository
* </ul>
*/
private static boolean validateStringEmpty(
List<BuilderProblem> problems, String fieldName, String string, String message) {
ProblemCollector<BuilderProblem> problems, String fieldName, String string, String message) {
if (string == null || string.length() == 0) {
return true;
}
@ -287,7 +289,7 @@ private static boolean validateStringEmpty(
* </ul>
*/
private static boolean validateStringNotEmpty(
List<BuilderProblem> problems, String fieldName, String string, String sourceHint) {
ProblemCollector<BuilderProblem> problems, String fieldName, String string, String sourceHint) {
if (!validateNotNull(problems, fieldName, string, sourceHint)) {
return false;
}
@ -309,7 +311,7 @@ private static boolean validateStringNotEmpty(
* </ul>
*/
private static boolean validateNotNull(
List<BuilderProblem> problems, String fieldName, Object object, String sourceHint) {
ProblemCollector<BuilderProblem> problems, String fieldName, Object object, String sourceHint) {
if (object != null) {
return true;
}
@ -320,7 +322,7 @@ private static boolean validateNotNull(
}
private static boolean validateBannedCharacters(
List<BuilderProblem> problems,
ProblemCollector<BuilderProblem> problems,
String fieldName,
BuilderProblem.Severity severity,
String string,
@ -344,7 +346,7 @@ private static boolean validateBannedCharacters(
}
private static void addViolation(
List<BuilderProblem> problems,
ProblemCollector<BuilderProblem> problems,
BuilderProblem.Severity severity,
String fieldName,
String sourceHint,
@ -358,6 +360,6 @@ private static void addViolation(
buffer.append(' ').append(message);
problems.add(new DefaultBuilderProblem(null, -1, -1, null, buffer.toString(), severity));
problems.reportProblem(new DefaultBuilderProblem(null, -1, -1, null, buffer.toString(), severity));
}
}

View File

@ -23,8 +23,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@ -32,6 +30,7 @@
import org.apache.maven.api.di.Named;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.Interpolator;
import org.apache.maven.api.services.ProblemCollector;
import org.apache.maven.api.services.Source;
import org.apache.maven.api.services.ToolchainsBuilder;
import org.apache.maven.api.services.ToolchainsBuilderException;
@ -70,7 +69,7 @@ public DefaultToolchainsBuilder(Interpolator interpolator, ToolchainsXmlFactory
@Override
public ToolchainsBuilderResult build(ToolchainsBuilderRequest request) throws ToolchainsBuilderException {
List<BuilderProblem> problems = new ArrayList<>();
ProblemCollector<BuilderProblem> problems = ProblemCollector.create(request.getSession());
Source installationSource = request.getInstallationToolchainsSource().orElse(null);
PersistedToolchains installation = readToolchains(installationSource, request, problems);
@ -80,27 +79,15 @@ public ToolchainsBuilderResult build(ToolchainsBuilderRequest request) throws To
PersistedToolchains effective = toolchainsMerger.merge(user, installation, false, null);
if (hasErrors(problems)) {
if (problems.hasErrorProblems()) {
throw new ToolchainsBuilderException("Error building toolchains", problems);
}
return new DefaultToolchainsBuilderResult(effective, problems);
}
private boolean hasErrors(List<BuilderProblem> problems) {
if (problems != null) {
for (BuilderProblem problem : problems) {
if (BuilderProblem.Severity.ERROR.compareTo(problem.getSeverity()) >= 0) {
return true;
}
}
}
return false;
}
private PersistedToolchains readToolchains(
Source toolchainsSource, ToolchainsBuilderRequest request, List<BuilderProblem> problems) {
Source toolchainsSource, ToolchainsBuilderRequest request, ProblemCollector<BuilderProblem> problems) {
if (toolchainsSource == null) {
return PersistedToolchains.newInstance();
}
@ -129,7 +116,7 @@ private PersistedToolchains readToolchains(
.strict(false)
.build());
Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
problems.add(new DefaultBuilderProblem(
problems.reportProblem(new DefaultBuilderProblem(
toolchainsSource.getLocation(),
loc != null ? loc.getLineNumber() : -1,
loc != null ? loc.getColumnNumber() : -1,
@ -139,7 +126,7 @@ private PersistedToolchains readToolchains(
}
} catch (XmlReaderException e) {
Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
problems.add(new DefaultBuilderProblem(
problems.reportProblem(new DefaultBuilderProblem(
toolchainsSource.getLocation(),
loc != null ? loc.getLineNumber() : -1,
loc != null ? loc.getColumnNumber() : -1,
@ -148,7 +135,7 @@ private PersistedToolchains readToolchains(
BuilderProblem.Severity.FATAL));
return PersistedToolchains.newInstance();
} catch (IOException e) {
problems.add(new DefaultBuilderProblem(
problems.reportProblem(new DefaultBuilderProblem(
toolchainsSource.getLocation(),
-1,
-1,
@ -164,7 +151,9 @@ private PersistedToolchains readToolchains(
}
private PersistedToolchains interpolate(
PersistedToolchains toolchains, ToolchainsBuilderRequest request, List<BuilderProblem> problems) {
PersistedToolchains toolchains,
ToolchainsBuilderRequest request,
ProblemCollector<BuilderProblem> problems) {
Map<String, String> userProperties = request.getSession().getUserProperties();
Map<String, String> systemProperties = request.getSession().getSystemProperties();
Function<String, String> src = Interpolator.chain(userProperties::get, systemProperties::get);
@ -180,11 +169,12 @@ static class DefaultToolchainsBuilderResult implements ToolchainsBuilderResult {
private final PersistedToolchains effectiveToolchains;
private final List<BuilderProblem> problems;
private final ProblemCollector<BuilderProblem> problems;
DefaultToolchainsBuilderResult(PersistedToolchains effectiveToolchains, List<BuilderProblem> problems) {
DefaultToolchainsBuilderResult(
PersistedToolchains effectiveToolchains, ProblemCollector<BuilderProblem> problems) {
this.effectiveToolchains = effectiveToolchains;
this.problems = (problems != null) ? problems : new ArrayList<>();
this.problems = (problems != null) ? problems : ProblemCollector.empty();
}
@Override
@ -193,7 +183,7 @@ public PersistedToolchains getEffectiveToolchains() {
}
@Override
public List<BuilderProblem> getProblems() {
public ProblemCollector<BuilderProblem> getProblems() {
return problems;
}
}

View File

@ -81,6 +81,7 @@
import org.apache.maven.api.services.ModelProblem.Version;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.ModelSource;
import org.apache.maven.api.services.ProblemCollector;
import org.apache.maven.api.services.RepositoryFactory;
import org.apache.maven.api.services.Source;
import org.apache.maven.api.services.SuperPomProvider;
@ -205,10 +206,6 @@ public ModelBuilderSession newSession() {
return new ModelBuilderSessionImpl();
}
static int getMaxProblems(Session session) {
return Integer.parseInt(session.getUserProperties().getOrDefault(Constants.MAVEN_BUILDER_MAX_PROBLEMS, "100"));
}
protected class ModelBuilderSessionImpl implements ModelBuilderSession {
ModelBuilderSessionState mainSession;
@ -227,8 +224,8 @@ public ModelBuilderResult build(ModelBuilderRequest request) throws ModelBuilder
mainSession = new ModelBuilderSessionState(request);
session = mainSession;
} else {
session =
mainSession.derive(request, new DefaultModelBuilderResult(getMaxProblems(mainSession.session)));
session = mainSession.derive(
request, new DefaultModelBuilderResult(ProblemCollector.create(mainSession.session)));
}
// Build the request
if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
@ -264,7 +261,7 @@ protected class ModelBuilderSessionState implements ModelProblemCollector {
this(
request.getSession(),
request,
new DefaultModelBuilderResult(DefaultModelBuilder.getMaxProblems(request.getSession())),
new DefaultModelBuilderResult(ProblemCollector.create(request.getSession())),
request.getSession()
.getData()
.computeIfAbsent(SessionData.key(ModelCache.class), modelCacheFactory::newInstance),
@ -305,12 +302,8 @@ private ModelBuilderSessionState(
this.result.setSource(this.request.getSource());
}
int getMaxProblems() {
return DefaultModelBuilder.getMaxProblems(session);
}
ModelBuilderSessionState derive(ModelSource source) {
return derive(source, new DefaultModelBuilderResult(result, getMaxProblems()));
return derive(source, new DefaultModelBuilderResult(ProblemCollector.create(session)));
}
ModelBuilderSessionState derive(ModelSource source, DefaultModelBuilderResult result) {
@ -321,7 +314,7 @@ ModelBuilderSessionState derive(ModelSource source, DefaultModelBuilderResult re
* Creates a new session, sharing cached datas and propagating errors.
*/
ModelBuilderSessionState derive(ModelBuilderRequest request) {
return derive(request, new DefaultModelBuilderResult(result, getMaxProblems()));
return derive(request, new DefaultModelBuilderResult(ProblemCollector.create(session)));
}
ModelBuilderSessionState derive(ModelBuilderRequest request, DefaultModelBuilderResult result) {
@ -433,18 +426,9 @@ public void putSource(String groupId, String artifactId, ModelSource source) {
}
}
public boolean hasFatalErrors() {
return result.getProblems().stream().anyMatch(p -> p.getSeverity() == Severity.FATAL);
}
public boolean hasErrors() {
return result.getProblems().stream()
.anyMatch(p -> p.getSeverity() == Severity.FATAL || p.getSeverity() == Severity.ERROR);
}
@Override
public List<ModelProblem> getProblems() {
return result.getProblems();
public ProblemCollector<ModelProblem> getProblemCollector() {
return result.getProblemCollector();
}
public void setSource(String source) {
@ -481,30 +465,6 @@ public Model getRootModel() {
}
@Override
public void add(ModelProblem problem) {
result.addProblem(problem);
}
@Override
public void add(BuilderProblem.Severity severity, ModelProblem.Version version, String message) {
add(severity, version, message, null, null);
}
@Override
public void add(
BuilderProblem.Severity severity,
ModelProblem.Version version,
String message,
InputLocation location) {
add(severity, version, message, location, null);
}
@Override
public void add(
BuilderProblem.Severity severity, ModelProblem.Version version, String message, Exception exception) {
add(severity, version, message, null, exception);
}
public void add(
BuilderProblem.Severity severity,
ModelProblem.Version version,
@ -720,8 +680,9 @@ Stream<DefaultModelBuilderResult> results(DefaultModelBuilderResult r) {
private void loadFromRoot(Path root, Path top) {
try (PhasingExecutor executor = createExecutor()) {
DefaultModelBuilderResult r =
Objects.equals(top, root) ? result : new DefaultModelBuilderResult(getMaxProblems());
DefaultModelBuilderResult r = Objects.equals(top, root)
? result
: new DefaultModelBuilderResult(ProblemCollector.create(session));
loadFilePom(executor, top, root, Set.of(), r);
}
if (result.getFileModel() == null && !Objects.equals(top, root)) {
@ -767,7 +728,7 @@ private void loadFilePom(
-1,
-1,
null);
r.addProblem(problem);
r.getProblemCollector().reportProblem(problem);
continue;
}
@ -789,13 +750,13 @@ private void loadFilePom(
-1,
-1,
null);
r.addProblem(problem);
r.getProblemCollector().reportProblem(problem);
continue;
}
DefaultModelBuilderResult cr = Objects.equals(top, subprojectFile)
? result
: new DefaultModelBuilderResult(r, getMaxProblems());
: new DefaultModelBuilderResult(ProblemCollector.create(session));
if (request.isRecursive()) {
r.getChildren().add(cr);
}
@ -806,9 +767,6 @@ private void loadFilePom(
// gathered with problem collector
add(Severity.ERROR, Version.V40, "Failed to load project " + pom, e);
}
if (r != result) {
r.getProblems().forEach(result::addProblem);
}
}
static <T> Set<T> concat(Set<T> a, T b) {
@ -1731,12 +1689,9 @@ private Model doLoadDependencyManagement(
modelBuilderSession.buildEffectiveModel(importIds);
importResult = modelBuilderSession.result;
} catch (ModelBuilderException e) {
e.getResult().getProblems().forEach(this::add);
return null;
}
importResult.getProblems().forEach(this::add);
importModel = importResult.getEffectiveModel();
return importModel;
@ -1858,6 +1813,7 @@ private static List<String> getSubprojects(Model activated) {
return subprojects;
}
@Override
public Model buildRawModel(ModelBuilderRequest request) throws ModelBuilderException {
ModelBuilderSessionState build = new ModelBuilderSessionState(request);
Model model = build.readRawModel();

View File

@ -20,20 +20,15 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ModelBuilderResult;
import org.apache.maven.api.services.ModelProblem;
import org.apache.maven.api.services.ModelSource;
import org.apache.maven.api.services.ProblemCollector;
/**
* Collects the output of the model builder.
@ -46,23 +41,14 @@ class DefaultModelBuilderResult implements ModelBuilderResult {
private Model effectiveModel;
private List<Profile> activePomProfiles;
private List<Profile> activeExternalProfiles;
private final Queue<ModelProblem> problems = new ConcurrentLinkedQueue<>();
private final DefaultModelBuilderResult problemHolder;
private final ProblemCollector<ModelProblem> problemCollector;
private final List<DefaultModelBuilderResult> children = new ArrayList<>();
private int maxProblems;
private Map<BuilderProblem.Severity, AtomicInteger> problemCount = new ConcurrentHashMap<>();
DefaultModelBuilderResult(int maxProblems) {
this(null, maxProblems);
}
DefaultModelBuilderResult(DefaultModelBuilderResult problemHolder, int maxProblems) {
this.problemHolder = problemHolder;
this.maxProblems = maxProblems;
DefaultModelBuilderResult(ProblemCollector<ModelProblem> problemCollector) {
this.problemCollector = problemCollector;
}
@Override
public ModelSource getSource() {
return source;
}
@ -132,39 +118,8 @@ public void setActiveExternalProfiles(List<Profile> activeProfiles) {
* guaranteed to be non-null but possibly empty.
*/
@Override
public List<ModelProblem> getProblems() {
List<ModelProblem> additionalProblems = new ArrayList<>();
problemCount.forEach((s, i) -> {
if (i.get() > maxProblems) {
additionalProblems.add(new DefaultModelProblem(
String.format("Too many problems %d of severity %s", i.get(), s.name()),
s,
ModelProblem.Version.BASE,
null,
-1,
-1,
null,
null));
}
});
return Stream.concat(problems.stream(), additionalProblems.stream()).toList();
}
/**
* Adds a given problem to the list of problems and propagates it to the parent result if present.
*
* @param problem The problem to be added. It must be an instance of ModelProblem.
*/
public void addProblem(ModelProblem problem) {
int problemCount = this.problemCount
.computeIfAbsent(problem.getSeverity(), s -> new AtomicInteger())
.incrementAndGet();
if (problemCount < maxProblems) {
problems.add(problem);
}
if (problemHolder != null) {
problemHolder.addProblem(problem);
}
public ProblemCollector<ModelProblem> getProblemCollector() {
return problemCollector;
}
@Override
@ -183,18 +138,19 @@ public String toString() {
} else {
modelId = null;
}
if (!problems.isEmpty()) {
if (problemCollector.hasWarningProblems()) {
int totalProblems = problemCollector.totalProblemsReported();
StringBuilder sb = new StringBuilder();
sb.append(problems.size())
sb.append(totalProblems)
.append(
(problems.size() == 1)
(totalProblems == 1)
? " problem was "
: " problems were encountered while building the effective model");
if (modelId != null && !modelId.isEmpty()) {
sb.append(" for ");
sb.append(modelId);
}
for (ModelProblem problem : problems) {
for (ModelProblem problem : problemCollector.problems().toList()) {
sb.append(System.lineSeparator());
sb.append(" - [");
sb.append(problem.getSeverity());

View File

@ -1,230 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.internal.impl.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.maven.api.services.BuilderProblem.Severity;
import org.apache.maven.api.services.ModelProblem;
/**
* There are various forms of results that are represented by this class:
* <ol>
* <li>success - in which case only the model field is set
* <li>success with warnings - model field + non-error model problems
* <li>error - no model, but diagnostics
* <li>error - (partial) model and diagnostics
* </ol>
* Could encode these variants as subclasses, but kept in one for now
*
* @param <T> the model type
*/
public class Result<T> {
/**
* Success without warnings
*
* @param model
*/
public static <T> Result<T> success(T model) {
return success(model, Collections.emptyList());
}
/**
* Success with warnings
*
* @param model
* @param problems
*/
public static <T> Result<T> success(T model, Iterable<? extends ModelProblem> problems) {
assert !hasErrors(problems);
return new Result<>(false, model, problems);
}
/**
* Success with warnings
*
* @param model
* @param results
*/
public static <T> Result<T> success(T model, Result<?>... results) {
final List<ModelProblem> problemsList = new ArrayList<>();
for (Result<?> result1 : results) {
for (ModelProblem modelProblem : result1.getProblems()) {
problemsList.add(modelProblem);
}
}
return success(model, problemsList);
}
/**
* Error with problems describing the cause
*
* @param problems
*/
public static <T> Result<T> error(Iterable<? extends ModelProblem> problems) {
return error(null, problems);
}
public static <T> Result<T> error(T model) {
return error(model, Collections.emptyList());
}
public static <T> Result<T> error(Result<?> result) {
return error(result.getProblems());
}
public static <T> Result<T> error(Result<?>... results) {
final List<ModelProblem> problemsList = new ArrayList<>();
for (Result<?> result1 : results) {
for (ModelProblem modelProblem : result1.getProblems()) {
problemsList.add(modelProblem);
}
}
return error(problemsList);
}
/**
* Error with partial result and problems describing the cause
*
* @param model
* @param problems
*/
public static <T> Result<T> error(T model, Iterable<? extends ModelProblem> problems) {
return new Result<>(true, model, problems);
}
/**
* New result - determine whether error or success by checking problems for errors
*
* @param model
* @param problems
*/
public static <T> Result<T> newResult(T model, Iterable<? extends ModelProblem> problems) {
return new Result<>(hasErrors(problems), model, problems);
}
/**
* New result consisting of given result and new problem. Convenience for newResult(result.get(),
* concat(result.getProblems(),problems)).
*
* @param result
* @param problem
*/
public static <T> Result<T> addProblem(Result<T> result, ModelProblem problem) {
return addProblems(result, Collections.singleton(problem));
}
/**
* New result that includes the given
*
* @param result
* @param problems
*/
public static <T> Result<T> addProblems(Result<T> result, Iterable<? extends ModelProblem> problems) {
Collection<ModelProblem> list = new ArrayList<>();
for (ModelProblem item : problems) {
list.add(item);
}
for (ModelProblem item : result.getProblems()) {
list.add(item);
}
return new Result<>(result.hasErrors() || hasErrors(problems), result.get(), list);
}
public static <T> Result<T> addProblems(Result<T> result, Result<?>... results) {
final List<ModelProblem> problemsList = new ArrayList<>();
for (Result<?> result1 : results) {
for (ModelProblem modelProblem : result1.getProblems()) {
problemsList.add(modelProblem);
}
}
return addProblems(result, problemsList);
}
/**
* Turns the given results into a single result by combining problems and models into single collection.
*
* @param results
*/
public static <T> Result<Iterable<T>> newResultSet(Iterable<? extends Result<? extends T>> results) {
boolean hasErrors = false;
List<T> modelsList = new ArrayList<>();
List<ModelProblem> problemsList = new ArrayList<>();
for (Result<? extends T> result : results) {
modelsList.add(result.get());
for (ModelProblem modelProblem : result.getProblems()) {
problemsList.add(modelProblem);
}
if (result.hasErrors()) {
hasErrors = true;
}
}
return new Result<>(hasErrors, (Iterable<T>) modelsList, problemsList);
}
// helper to determine if problems contain error
private static boolean hasErrors(Iterable<? extends ModelProblem> problems) {
for (ModelProblem input : problems) {
if (input.getSeverity().equals(Severity.ERROR)
|| input.getSeverity().equals(Severity.FATAL)) {
return true;
}
}
return false;
}
/**
* Class definition
*/
private final boolean errors;
private final T value;
private final Iterable<? extends ModelProblem> problems;
private Result(boolean errors, T model, Iterable<? extends ModelProblem> problems) {
this.errors = errors;
this.value = model;
this.problems = problems;
}
public Iterable<? extends ModelProblem> getProblems() {
return problems;
}
public T get() {
return value;
}
public boolean hasErrors() {
return errors;
}
}

View File

@ -35,6 +35,7 @@
import org.apache.maven.api.services.ModelBuilderResult;
import org.apache.maven.api.services.ModelProblem;
import org.apache.maven.api.services.ModelSource;
import org.apache.maven.api.services.ProblemCollector;
import org.apache.maven.api.services.model.ModelResolverException;
import org.apache.maven.internal.impl.InternalSession;
import org.apache.maven.internal.impl.model.ModelProblemUtils;
@ -207,20 +208,22 @@ private Model loadPom(
ModelBuilderResult modelResult = modelBuilder.newSession().build(modelRequest);
// ModelBuildingEx is thrown only on FATAL and ERROR severities, but we still can have WARNs
// that may lead to unexpected build failure, log them
if (!modelResult.getProblems().isEmpty()) {
List<ModelProblem> problems = modelResult.getProblems();
if (modelResult.getProblemCollector().hasWarningProblems()) {
ProblemCollector<ModelProblem> problemCollector = modelResult.getProblemCollector();
int totalProblems = problemCollector.totalProblemsReported();
if (logger.isDebugEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append(problems.size())
sb.append(totalProblems)
.append(" ")
.append((problems.size() == 1) ? "problem was" : "problems were")
.append(" encountered while building the effective model for ")
.append((totalProblems == 1) ? "problem was" : "problems were")
.append(" encountered while building the effective model for '")
.append(request.getArtifact())
.append(" during ")
.append("' during ")
.append(RequestTraceHelper.interpretTrace(true, request.getTrace()))
.append("\n")
.append((problems.size() == 1) ? "Problem" : "Problems");
for (ModelProblem modelProblem : problems) {
.append((totalProblems == 1) ? "Problem" : "Problems");
for (ModelProblem modelProblem :
problemCollector.problems().toList()) {
sb.append("\n* ")
.append(modelProblem.getMessage())
.append(" @ ")
@ -229,16 +232,17 @@ private Model loadPom(
logger.warn(sb.toString());
} else {
logger.warn(
"{} {} encountered while building the effective model for {} during {} (use -X to see details)",
problems.size(),
(problems.size() == 1) ? "problem was" : "problems were",
"{} {} encountered while building the effective model for '{}' during {} (use -X to see details)",
totalProblems,
(totalProblems == 1) ? "problem was" : "problems were",
request.getArtifact(),
RequestTraceHelper.interpretTrace(false, request.getTrace()));
}
}
model = modelResult.getEffectiveModel();
} catch (ModelBuilderException e) {
for (ModelProblem problem : e.getResult().getProblems()) {
for (ModelProblem problem :
e.getResult().getProblemCollector().problems().toList()) {
if (problem.getException() instanceof ModelResolverException) {
result.addException(problem.getException());
throw new ArtifactDescriptorException(result);

View File

@ -109,8 +109,7 @@ public VersionResult resolveVersion(RepositorySystemSession session, VersionRequ
cacheKey = new Key(session, request);
Object obj = cache.get(session, cacheKey);
if (obj instanceof Record) {
Record record = (Record) obj;
if (obj instanceof Record record) {
result.setVersion(record.version);
result.setRepository(
getRepository(session, request.getRepositories(), record.repoClass, record.repoId));
@ -189,8 +188,7 @@ public VersionResult resolveVersion(RepositorySystemSession session, VersionRequ
if (result.getVersion() != null && result.getVersion().endsWith(SNAPSHOT)) {
VersionRequest subRequest = new VersionRequest();
subRequest.setArtifact(artifact.setVersion(result.getVersion()));
if (result.getRepository() instanceof RemoteRepository) {
RemoteRepository r = (RemoteRepository) result.getRepository();
if (result.getRepository() instanceof RemoteRepository r) {
subRequest.setRepositories(Collections.singletonList(r));
} else {
subRequest.setRepositories(request.getRepositories());

View File

@ -0,0 +1,162 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.internal.impl;
import java.util.stream.IntStream;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ProblemCollector;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* This UT is for {@link ProblemCollector} but here we have implementations for problems.
*/
class DefaultProblemCollectorTest {
@Test
void severityFatalDetection() {
ProblemCollector<BuilderProblem> collector = ProblemCollector.create(5);
assertFalse(collector.hasProblemsFor(BuilderProblem.Severity.WARNING));
assertFalse(collector.hasErrorProblems());
assertFalse(collector.hasFatalProblems());
collector.reportProblem(
new DefaultBuilderProblem("source", 0, 0, null, "message", BuilderProblem.Severity.FATAL));
// fatal triggers all
assertTrue(collector.hasProblemsFor(BuilderProblem.Severity.WARNING));
assertTrue(collector.hasErrorProblems());
assertTrue(collector.hasFatalProblems());
}
@Test
void severityErrorDetection() {
ProblemCollector<BuilderProblem> collector = ProblemCollector.create(5);
assertFalse(collector.hasProblemsFor(BuilderProblem.Severity.WARNING));
assertFalse(collector.hasErrorProblems());
assertFalse(collector.hasFatalProblems());
collector.reportProblem(
new DefaultBuilderProblem("source", 0, 0, null, "message", BuilderProblem.Severity.ERROR));
// error triggers error + warning
assertTrue(collector.hasProblemsFor(BuilderProblem.Severity.WARNING));
assertTrue(collector.hasErrorProblems());
assertFalse(collector.hasFatalProblems());
}
@Test
void severityWarningDetection() {
ProblemCollector<BuilderProblem> collector = ProblemCollector.create(5);
assertFalse(collector.hasProblemsFor(BuilderProblem.Severity.WARNING));
assertFalse(collector.hasErrorProblems());
assertFalse(collector.hasFatalProblems());
collector.reportProblem(
new DefaultBuilderProblem("source", 0, 0, null, "message", BuilderProblem.Severity.WARNING));
// warning triggers warning only
assertTrue(collector.hasProblemsFor(BuilderProblem.Severity.WARNING));
assertFalse(collector.hasErrorProblems());
assertFalse(collector.hasFatalProblems());
}
@Test
void lossy() {
ProblemCollector<BuilderProblem> collector = ProblemCollector.create(5);
IntStream.range(0, 5)
.forEach(i -> collector.reportProblem(new DefaultBuilderProblem(
"source", 0, 0, null, "message " + i, BuilderProblem.Severity.WARNING)));
// collector is "full" of warnings
assertFalse(collector.reportProblem(
new DefaultBuilderProblem("source", 0, 0, null, "message", BuilderProblem.Severity.WARNING)));
// but collector will drop warning for more severe issues
assertTrue(collector.reportProblem(
new DefaultBuilderProblem("source", 0, 0, null, "message", BuilderProblem.Severity.ERROR)));
assertTrue(collector.reportProblem(
new DefaultBuilderProblem("source", 0, 0, null, "message", BuilderProblem.Severity.FATAL)));
// collector is full of warnings, errors and fatal (mixed)
assertFalse(collector.reportProblem(
new DefaultBuilderProblem("source", 0, 0, null, "message", BuilderProblem.Severity.WARNING)));
// fill it up with fatal ones
IntStream.range(0, 5)
.forEach(i -> collector.reportProblem(new DefaultBuilderProblem(
"source", 0, 0, null, "message " + i, BuilderProblem.Severity.FATAL)));
// from now on, only counters work, problems are lost
assertFalse(collector.reportProblem(
new DefaultBuilderProblem("source", 0, 0, null, "message", BuilderProblem.Severity.WARNING)));
assertFalse(collector.reportProblem(
new DefaultBuilderProblem("source", 0, 0, null, "message", BuilderProblem.Severity.ERROR)));
assertFalse(collector.reportProblem(
new DefaultBuilderProblem("source", 0, 0, null, "message", BuilderProblem.Severity.FATAL)));
assertEquals(17, collector.totalProblemsReported());
assertEquals(8, collector.problemsReportedFor(BuilderProblem.Severity.WARNING));
assertEquals(2, collector.problemsReportedFor(BuilderProblem.Severity.ERROR));
assertEquals(7, collector.problemsReportedFor(BuilderProblem.Severity.FATAL));
// but preserved problems count == capacity
assertEquals(5, collector.problems().count());
}
@Test
void moreSeverePushOutLeastSevere() {
ProblemCollector<BuilderProblem> collector = ProblemCollector.create(5);
assertEquals(0, collector.totalProblemsReported());
assertEquals(0, collector.problems().count());
IntStream.range(0, 5)
.forEach(i -> collector.reportProblem(new DefaultBuilderProblem(
"source", 0, 0, null, "message " + i, BuilderProblem.Severity.WARNING)));
assertEquals(5, collector.totalProblemsReported());
assertEquals(5, collector.problems().count());
IntStream.range(0, 5)
.forEach(i -> collector.reportProblem(new DefaultBuilderProblem(
"source", 0, 0, null, "message " + i, BuilderProblem.Severity.ERROR)));
assertEquals(10, collector.totalProblemsReported());
assertEquals(5, collector.problems().count());
IntStream.range(0, 4)
.forEach(i -> collector.reportProblem(new DefaultBuilderProblem(
"source", 0, 0, null, "message " + i, BuilderProblem.Severity.FATAL)));
assertEquals(14, collector.totalProblemsReported());
assertEquals(5, collector.problems().count());
assertEquals(5, collector.problemsReportedFor(BuilderProblem.Severity.WARNING));
assertEquals(5, collector.problemsReportedFor(BuilderProblem.Severity.ERROR));
assertEquals(4, collector.problemsReportedFor(BuilderProblem.Severity.FATAL));
assertEquals(0, collector.problems(BuilderProblem.Severity.WARNING).count());
assertEquals(1, collector.problems(BuilderProblem.Severity.ERROR).count());
assertEquals(4, collector.problems(BuilderProblem.Severity.FATAL).count());
}
}

View File

@ -21,6 +21,7 @@
import java.util.List;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ProblemCollector;
import org.apache.maven.api.services.SettingsBuilder;
import org.apache.maven.api.settings.Profile;
import org.apache.maven.api.settings.Repository;
@ -56,32 +57,29 @@ private void assertContains(String msg, String substring) {
void testValidate() {
Profile prof = Profile.newBuilder().id("xxx").build();
Settings model = Settings.newBuilder().profiles(List.of(prof)).build();
List<BuilderProblem> problems = validator.validate(model);
assertEquals(0, problems.size());
ProblemCollector<BuilderProblem> problems = validator.validate(model);
assertEquals(0, problems.totalProblemsReported());
Repository repo = org.apache.maven.api.settings.Repository.newInstance(false);
Settings model2 = Settings.newBuilder()
.profiles(List.of(prof.withRepositories(List.of(repo))))
.build();
problems.clear();
problems = validator.validate(model2);
assertEquals(2, problems.size());
assertEquals(2, problems.totalProblemsReported());
repo = repo.withUrl("http://xxx.xxx.com");
model2 = Settings.newBuilder()
.profiles(List.of(prof.withRepositories(List.of(repo))))
.build();
problems.clear();
problems = validator.validate(model2);
assertEquals(1, problems.size());
assertEquals(1, problems.totalProblemsReported());
repo = repo.withId("xxx");
model2 = Settings.newBuilder()
.profiles(List.of(prof.withRepositories(List.of(repo))))
.build();
problems.clear();
problems = validator.validate(model2);
assertEquals(0, problems.size());
assertEquals(0, problems.totalProblemsReported());
}
/*

View File

@ -76,7 +76,8 @@ public void testConditionExistingAndMissingInActivation() throws Exception {
.build();
ModelBuilderResult result = builder.newSession().build(request);
assertNotNull(result);
assertTrue(result.getProblems().stream()
assertTrue(result.getProblemCollector()
.problems()
.anyMatch(p -> p.getSeverity() == BuilderProblem.Severity.WARNING
&& p.getMessage().contains("The 'missing' assertion will be ignored.")));
}

View File

@ -18,7 +18,6 @@
*/
package org.apache.maven.internal.impl.model.profile;
import java.util.ArrayList;
import java.util.List;
import org.apache.maven.api.model.InputLocation;
@ -27,6 +26,7 @@
import org.apache.maven.api.services.ModelBuilderException;
import org.apache.maven.api.services.ModelProblem;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.ProblemCollector;
import org.apache.maven.internal.impl.model.DefaultModelProblem;
/**
@ -34,23 +34,11 @@
*/
public class SimpleProblemCollector implements ModelProblemCollector {
final List<ModelProblem> problems = new ArrayList<>();
final ProblemCollector<ModelProblem> problemCollector = ProblemCollector.create(100);
@Override
public List<ModelProblem> getProblems() {
return problems;
}
@Override
public boolean hasErrors() {
return problems.stream()
.anyMatch(p -> p.getSeverity() == ModelProblem.Severity.FATAL
|| p.getSeverity() == ModelProblem.Severity.ERROR);
}
@Override
public boolean hasFatalErrors() {
return problems.stream().anyMatch(p -> p.getSeverity() == ModelProblem.Severity.FATAL);
public ProblemCollector<ModelProblem> getProblemCollector() {
return problemCollector;
}
@Override
@ -70,11 +58,6 @@ public void add(
exception));
}
@Override
public void add(ModelProblem problem) {
this.problems.add(problem);
}
@Override
public ModelBuilderException newModelBuilderException() {
throw new UnsupportedOperationException();
@ -118,8 +101,8 @@ public List<String> getWarnings() {
}
private List<String> getForLevel(BuilderProblem.Severity severity) {
return problems.stream()
.filter(p -> p.getSeverity() == severity)
return problemCollector
.problems(severity)
.map(BuilderProblem::getMessage)
.toList();
}

View File

@ -47,8 +47,7 @@ void testLegacy() throws Exception {
verifier.verifyErrorFreeLog();
// there is a warning and all fields decrypted
verifier.verifyTextInLog(
"[INFO] Some problems were encountered while building the effective settings (use -X to see details)");
verifier.verifyTextInLog(" encountered while building the effective settings (use -e to see details)");
verifier.verifyTextInLog("<password>testtest</password>");
verifier.verifyTextInLog("<value>testtest</value>");
}
@ -71,8 +70,7 @@ void testModern() throws Exception {
// there is no warning and all fields decrypted
verifier.verifyTextNotInLog("[WARNING]");
verifier.verifyTextNotInLog(
"[INFO] Some problems were encountered while building the effective settings (use -X to see details)");
verifier.verifyTextNotInLog(" encountered while building the effective settings (use -e to see details)");
verifier.verifyTextInLog("<password>testtest</password>");
verifier.verifyTextInLog("<value>secretHeader</value>");
}