From 399f8b4ffce829837a6ea747eaab070061693653 Mon Sep 17 00:00:00 2001 From: Matt Benson Date: Thu, 2 May 2024 08:13:07 -0500 Subject: [PATCH] [MNG-8081] interpolate available properties during default profile selection (Maven 4.x) (#1446) Co-authored-by: Guillaume Nodet --- .../impl/model/DefaultModelBuilder.java | 107 +++++--- .../impl/model/DefaultModelValidator.java | 257 ++++++++++++++++-- .../model/building/DefaultModelBuilder.java | 111 +++++--- .../validation/DefaultModelValidator.java | 256 +++++++++++++++-- .../validation/DefaultModelValidatorTest.java | 56 +++- ...tivation-file-with-allowed-expressions.xml | 18 ++ ...tion-property-with-project-expressions.xml | 49 ++++ 7 files changed, 717 insertions(+), 137 deletions(-) create mode 100644 maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-property-with-project-expressions.xml diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java index e10a05887f..43bdd649ee 100644 --- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java @@ -35,7 +35,9 @@ import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.maven.api.VersionRange; import org.apache.maven.api.annotations.Nullable; @@ -50,6 +52,7 @@ import org.apache.maven.api.model.Dependency; import org.apache.maven.api.model.DependencyManagement; import org.apache.maven.api.model.Exclusion; import org.apache.maven.api.model.InputLocation; +import org.apache.maven.api.model.InputLocationTracker; import org.apache.maven.api.model.InputSource; import org.apache.maven.api.model.Model; import org.apache.maven.api.model.Parent; @@ -80,8 +83,11 @@ import org.apache.maven.api.services.xml.XmlReaderRequest; import org.apache.maven.internal.impl.InternalSession; import org.apache.maven.internal.impl.resolver.DefaultModelCache; import org.apache.maven.internal.impl.resolver.DefaultModelResolver; +import org.apache.maven.model.v4.MavenTransformer; import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.Interpolator; import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; import org.codehaus.plexus.interpolation.StringSearchInterpolator; import org.eclipse.aether.impl.RemoteRepositoryManager; @@ -377,54 +383,75 @@ public class DefaultModelBuilder implements ModelBuilder { private List interpolateActivations( List profiles, DefaultProfileActivationContext context, DefaultModelProblemCollector problems) { - List newProfiles = null; - for (int index = 0; index < profiles.size(); index++) { - Profile profile = profiles.get(index); - Activation activation = profile.getActivation(); - if (activation != null) { - ActivationFile file = activation.getFile(); - if (file != null) { - String oldExists = file.getExists(); - if (isNotEmpty(oldExists)) { + if (profiles.stream() + .map(org.apache.maven.api.model.Profile::getActivation) + .noneMatch(Objects::nonNull)) { + return profiles; + } + final Interpolator xform = new RegexBasedInterpolator(); + xform.setCacheAnswers(true); + Stream.of(context.getUserProperties(), context.getSystemProperties()) + .map(MapBasedValueSource::new) + .forEach(xform::addValueSource); + + class ProfileInterpolator extends MavenTransformer implements UnaryOperator { + ProfileInterpolator() { + super(s -> { + if (isNotEmpty(s)) { try { - String newExists = interpolate(oldExists, context); - if (!Objects.equals(oldExists, newExists)) { - if (newProfiles == null) { - newProfiles = new ArrayList<>(profiles); - } - newProfiles.set( - index, profile.withActivation(activation.withFile(file.withExists(newExists)))); - } + return xform.interpolate(s); } catch (InterpolationException e) { - addInterpolationProblem(problems, file, oldExists, e, "exists"); - } - } else { - String oldMissing = file.getMissing(); - if (isNotEmpty(oldMissing)) { - try { - String newMissing = interpolate(oldMissing, context); - if (!Objects.equals(oldMissing, newMissing)) { - if (newProfiles == null) { - newProfiles = new ArrayList<>(profiles); - } - newProfiles.set( - index, - profile.withActivation(activation.withFile(file.withMissing(newMissing)))); - } - } catch (InterpolationException e) { - addInterpolationProblem(problems, file, oldMissing, e, "missing"); - } + problems.add(Severity.ERROR, ModelProblem.Version.BASE, e.getMessage(), e); } } + return s; + }); + } + + @Override + public Profile apply(Profile p) { + return Profile.newBuilder(p) + .activation(transformActivation(p.getActivation())) + .build(); + } + + @Override + protected ActivationFile.Builder transformActivationFile_Missing( + Supplier creator, + ActivationFile.Builder builder, + ActivationFile target) { + String path = target.getMissing(); + String xformed = transformPath(path, target, "missing"); + return xformed != path ? (builder != null ? builder : creator.get()).missing(xformed) : builder; + } + + @Override + protected ActivationFile.Builder transformActivationFile_Exists( + Supplier creator, + ActivationFile.Builder builder, + ActivationFile target) { + final String path = target.getExists(); + final String xformed = transformPath(path, target, "exists"); + return xformed != path ? (builder != null ? builder : creator.get()).exists(xformed) : builder; + } + + private String transformPath(String path, ActivationFile target, String locationKey) { + if (isNotEmpty(path)) { + try { + return profileActivationFilePathInterpolator.interpolate(path, context); + } catch (InterpolationException e) { + addInterpolationProblem(problems, target, path, e, locationKey); + } } + return path; } } - return newProfiles != null ? newProfiles : profiles; + return profiles.stream().map(new ProfileInterpolator()).toList(); } private static void addInterpolationProblem( DefaultModelProblemCollector problems, - ActivationFile file, + InputLocationTracker target, String path, InterpolationException e, String locationKey) { @@ -432,14 +459,10 @@ public class DefaultModelBuilder implements ModelBuilder { Severity.ERROR, ModelProblem.Version.BASE, "Failed to interpolate file location " + path + ": " + e.getMessage(), - file.getLocation(locationKey), + target.getLocation(locationKey), e); } - private String interpolate(String path, ProfileActivationContext context) throws InterpolationException { - return isNotEmpty(path) ? profileActivationFilePathInterpolator.interpolate(path, context) : path; - } - private static boolean isNotEmpty(String string) { return string != null && !string.isEmpty(); } diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java index 120a34e419..ce57f64433 100644 --- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java @@ -21,20 +21,31 @@ package org.apache.maven.internal.impl.model; import java.io.File; import java.util.Arrays; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.apache.maven.api.di.Inject; import org.apache.maven.api.di.Named; import org.apache.maven.api.di.Singleton; import org.apache.maven.api.model.Activation; import org.apache.maven.api.model.ActivationFile; +import org.apache.maven.api.model.ActivationOS; +import org.apache.maven.api.model.ActivationProperty; import org.apache.maven.api.model.Build; import org.apache.maven.api.model.BuildBase; import org.apache.maven.api.model.Dependency; @@ -59,6 +70,7 @@ import org.apache.maven.api.services.ModelProblem.Version; import org.apache.maven.api.services.ModelProblemCollector; import org.apache.maven.api.services.model.*; import org.apache.maven.model.v4.MavenModelVersion; +import org.apache.maven.model.v4.MavenTransformer; /** */ @@ -80,6 +92,196 @@ public class DefaultModelValidator implements ModelValidator { private static final String EMPTY = ""; + private record ActivationFrame(String location, Optional parent) {} + + private static class ActivationWalker extends MavenTransformer { + + private final Deque stk; + + ActivationWalker(Deque stk, UnaryOperator transformer) { + super(transformer); + this.stk = stk; + } + + private ActivationFrame nextFrame(String property) { + return new ActivationFrame(property, Optional.empty()); + } + + private

ActivationFrame nextFrame(String property, Function child) { + @SuppressWarnings("unchecked") + final Optional

parent = (Optional

) stk.peek().parent; + return new ActivationFrame(property, parent.map(child)); + } + + @Override + public Activation transformActivation(Activation target) { + stk.push(new ActivationFrame("activation", Optional.of(target))); + try { + return super.transformActivation(target); + } finally { + stk.pop(); + } + } + + @Override + protected Activation.Builder transformActivation_ActiveByDefault( + Supplier creator, Activation.Builder builder, Activation target) { + return builder; + } + + @Override + protected Activation.Builder transformActivation_File( + Supplier creator, Activation.Builder builder, Activation target) { + stk.push(nextFrame("file", Activation::getFile)); + Optional.ofNullable(target.getFile()); + try { + return super.transformActivation_File(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationFile.Builder transformActivationFile_Exists( + Supplier creator, + ActivationFile.Builder builder, + ActivationFile target) { + stk.push(nextFrame("exists")); + try { + return super.transformActivationFile_Exists(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationFile.Builder transformActivationFile_Missing( + Supplier creator, + ActivationFile.Builder builder, + ActivationFile target) { + stk.push(nextFrame("missing")); + try { + return super.transformActivationFile_Missing(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected Activation.Builder transformActivation_Jdk( + Supplier creator, Activation.Builder builder, Activation target) { + stk.push(nextFrame("jdk")); + try { + return super.transformActivation_Jdk(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected Activation.Builder transformActivation_Os( + Supplier creator, Activation.Builder builder, Activation target) { + stk.push(nextFrame("os", Activation::getOs)); + try { + return super.transformActivation_Os(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationOS.Builder transformActivationOS_Arch( + Supplier creator, ActivationOS.Builder builder, ActivationOS target) { + stk.push(nextFrame("arch")); + try { + return super.transformActivationOS_Arch(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationOS.Builder transformActivationOS_Family( + Supplier creator, ActivationOS.Builder builder, ActivationOS target) { + stk.push(nextFrame("family")); + try { + return super.transformActivationOS_Family(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationOS.Builder transformActivationOS_Name( + Supplier creator, ActivationOS.Builder builder, ActivationOS target) { + stk.push(nextFrame("name")); + try { + return super.transformActivationOS_Name(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationOS.Builder transformActivationOS_Version( + Supplier creator, ActivationOS.Builder builder, ActivationOS target) { + stk.push(nextFrame("version")); + try { + return super.transformActivationOS_Version(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected Activation.Builder transformActivation_Packaging( + Supplier creator, Activation.Builder builder, Activation target) { + stk.push(nextFrame("packaging")); + try { + return super.transformActivation_Packaging(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected Activation.Builder transformActivation_Property( + Supplier creator, Activation.Builder builder, Activation target) { + stk.push(nextFrame("property", Activation::getProperty)); + try { + return super.transformActivation_Property(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationProperty.Builder transformActivationProperty_Name( + Supplier creator, + ActivationProperty.Builder builder, + ActivationProperty target) { + stk.push(nextFrame("name")); + try { + return super.transformActivationProperty_Name(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationProperty.Builder transformActivationProperty_Value( + Supplier creator, + ActivationProperty.Builder builder, + ActivationProperty target) { + stk.push(nextFrame("value")); + try { + return super.transformActivationProperty_Value(creator, builder, target); + } finally { + stk.pop(); + } + } + } + private final Set validCoordinateIds = new HashSet<>(); private final Set validProfileIds = new HashSet<>(); @@ -282,42 +484,53 @@ public class DefaultModelValidator implements ModelValidator { } private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation, String prefix) { - if (activation == null || activation.getFile() == null) { + if (activation == null) { return; } - ActivationFile file = activation.getFile(); + final Deque stk = new LinkedList<>(); - String path; - String location; + final Supplier pathSupplier = () -> { + final boolean parallel = false; + return StreamSupport.stream(((Iterable) stk::descendingIterator).spliterator(), parallel) + .map(ActivationFrame::location) + .collect(Collectors.joining(".")); + }; + final Supplier locationSupplier = () -> { + if (stk.size() < 2) { + return null; + } + Iterator f = stk.iterator(); - if (file.getExists() != null && !file.getExists().isEmpty()) { - path = file.getExists(); - location = "exists"; - } else if (file.getMissing() != null && !file.getMissing().isEmpty()) { - path = file.getMissing(); - location = "missing"; - } else { - return; - } + String location = f.next().location; + ActivationFrame parent = f.next(); - if (hasProjectExpression(path)) { - Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(path); - while (matcher.find()) { - String propertyName = matcher.group(0); - if (!"${project.basedir}".equals(propertyName)) { + return parent.parent.map(p -> p.getLocation(location)).orElse(null); + }; + final UnaryOperator transformer = s -> { + if (hasProjectExpression(s)) { + String path = pathSupplier.get(); + Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(s); + while (matcher.find()) { + String propertyName = matcher.group(0); + + if (path.startsWith("activation.file.") && "${project.basedir}".equals(propertyName)) { + continue; + } addViolation( problems, Severity.WARNING, Version.V30, - prefix + "activation.file." + location, + prefix + path, null, - "Failed to interpolate file location " + path + ": " + propertyName + "Failed to interpolate profile activation property " + s + ": " + propertyName + " expressions are not supported during profile activation.", - file.getLocation(location)); + locationSupplier.get()); } } - } + return s; + }; + new ActivationWalker(stk, transformer).transformActivation(activation); } private void validate20RawPlugins( diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java index bc84b81c21..69ec67a2f4 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java @@ -38,10 +38,13 @@ import java.util.Optional; import java.util.Properties; import java.util.concurrent.Callable; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.maven.api.VersionRange; import org.apache.maven.api.feature.Features; +import org.apache.maven.api.model.ActivationFile; import org.apache.maven.api.model.Exclusion; import org.apache.maven.api.model.InputSource; import org.apache.maven.api.services.VersionParserException; @@ -57,6 +60,7 @@ import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginManagement; import org.apache.maven.model.Profile; import org.apache.maven.model.building.ModelProblem.Severity; +import org.apache.maven.model.building.ModelProblem.Version; import org.apache.maven.model.composition.DependencyManagementImporter; import org.apache.maven.model.inheritance.InheritanceAssembler; import org.apache.maven.model.interpolation.ModelInterpolator; @@ -82,11 +86,14 @@ import org.apache.maven.model.resolution.ModelResolver; import org.apache.maven.model.resolution.UnresolvableModelException; import org.apache.maven.model.resolution.WorkspaceModelResolver; import org.apache.maven.model.superpom.SuperPomProvider; +import org.apache.maven.model.v4.MavenTransformer; import org.apache.maven.model.validation.DefaultModelValidator; import org.apache.maven.model.validation.ModelValidator; import org.apache.maven.model.version.ModelVersionParser; import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.Interpolator; import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; import org.codehaus.plexus.interpolation.StringSearchInterpolator; import org.eclipse.sisu.Nullable; @@ -897,69 +904,89 @@ public class DefaultModelBuilder implements ModelBuilder { List profiles, DefaultProfileActivationContext context, DefaultModelProblemCollector problems) { - List newProfiles = null; - for (int index = 0; index < profiles.size(); index++) { - org.apache.maven.api.model.Profile profile = profiles.get(index); - org.apache.maven.api.model.Activation activation = profile.getActivation(); - if (activation != null) { - org.apache.maven.api.model.ActivationFile file = activation.getFile(); - if (file != null) { - String oldExists = file.getExists(); - if (isNotEmpty(oldExists)) { + if (profiles.stream() + .map(org.apache.maven.api.model.Profile::getActivation) + .noneMatch(Objects::nonNull)) { + return profiles; + } + final Interpolator xform = new RegexBasedInterpolator(); + xform.setCacheAnswers(true); + Stream.of(context.getUserProperties(), context.getSystemProperties()) + .map(MapBasedValueSource::new) + .forEach(xform::addValueSource); + + class ProfileInterpolator extends MavenTransformer + implements UnaryOperator { + ProfileInterpolator() { + super(s -> { + if (isNotEmpty(s)) { try { - String newExists = interpolate(oldExists, context); - if (!Objects.equals(oldExists, newExists)) { - if (newProfiles == null) { - newProfiles = new ArrayList<>(profiles); - } - newProfiles.set( - index, profile.withActivation(activation.withFile(file.withExists(newExists)))); - } + return xform.interpolate(s); } catch (InterpolationException e) { - addInterpolationProblem(problems, file, oldExists, e, "exists"); - } - } else { - String oldMissing = file.getMissing(); - if (isNotEmpty(oldMissing)) { - try { - String newMissing = interpolate(oldMissing, context); - if (!Objects.equals(oldMissing, newMissing)) { - if (newProfiles == null) { - newProfiles = new ArrayList<>(profiles); - } - newProfiles.set( - index, - profile.withActivation(activation.withFile(file.withMissing(newMissing)))); - } - } catch (InterpolationException e) { - addInterpolationProblem(problems, file, oldMissing, e, "missing"); - } + problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE) + .setMessage(e.getMessage()) + .setException(e)); } } + return s; + }); + } + + @Override + public org.apache.maven.api.model.Profile apply(org.apache.maven.api.model.Profile p) { + return org.apache.maven.api.model.Profile.newBuilder(p) + .activation(transformActivation(p.getActivation())) + .build(); + } + + @Override + protected ActivationFile.Builder transformActivationFile_Missing( + Supplier creator, + ActivationFile.Builder builder, + ActivationFile target) { + final String path = target.getMissing(); + final String xformed = transformPath(path, target, "missing"); + return xformed != path ? (builder != null ? builder : creator.get()).missing(xformed) : builder; + } + + @Override + protected ActivationFile.Builder transformActivationFile_Exists( + Supplier creator, + ActivationFile.Builder builder, + ActivationFile target) { + final String path = target.getExists(); + final String xformed = transformPath(path, target, "exists"); + return xformed != path ? (builder != null ? builder : creator.get()).exists(xformed) : builder; + } + + private String transformPath(String path, ActivationFile target, String locationKey) { + if (isNotEmpty(path)) { + try { + return profileActivationFilePathInterpolator.interpolate(path, context); + } catch (InterpolationException e) { + addInterpolationProblem(problems, target, path, e, locationKey); + } } + return path; } } - return newProfiles != null ? newProfiles : profiles; + return profiles.stream().map(new ProfileInterpolator()).toList(); } private static void addInterpolationProblem( DefaultModelProblemCollector problems, - org.apache.maven.api.model.ActivationFile file, + org.apache.maven.api.model.InputLocationTracker target, String path, InterpolationException e, String locationKey) { problems.add(new ModelProblemCollectorRequest(Severity.ERROR, ModelProblem.Version.BASE) .setMessage("Failed to interpolate file location " + path + ": " + e.getMessage()) - .setLocation(Optional.ofNullable(file.getLocation(locationKey)) + .setLocation(Optional.ofNullable(target.getLocation(locationKey)) .map(InputLocation::new) .orElse(null)) .setException(e)); } - private String interpolate(String path, ProfileActivationContext context) throws InterpolationException { - return isNotEmpty(path) ? profileActivationFilePathInterpolator.interpolate(path, context) : path; - } - private static boolean isNotEmpty(String string) { return string != null && !string.isEmpty(); } diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java b/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java index d2553c455d..246e2f8b13 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java @@ -25,17 +25,28 @@ import javax.inject.Singleton; import java.io.File; import java.util.Arrays; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.apache.maven.api.model.Activation; import org.apache.maven.api.model.ActivationFile; +import org.apache.maven.api.model.ActivationOS; +import org.apache.maven.api.model.ActivationProperty; import org.apache.maven.api.model.Build; import org.apache.maven.api.model.BuildBase; import org.apache.maven.api.model.Dependency; @@ -61,6 +72,7 @@ import org.apache.maven.model.building.ModelProblemCollector; import org.apache.maven.model.building.ModelProblemCollectorRequest; import org.apache.maven.model.interpolation.ModelVersionProcessor; import org.apache.maven.model.v4.MavenModelVersion; +import org.apache.maven.model.v4.MavenTransformer; /** */ @@ -82,6 +94,196 @@ public class DefaultModelValidator implements ModelValidator { private static final String EMPTY = ""; + private record ActivationFrame(String location, Optional parent) {} + + private static class ActivationWalker extends MavenTransformer { + + private final Deque stk; + + ActivationWalker(Deque stk, UnaryOperator transformer) { + super(transformer); + this.stk = stk; + } + + private ActivationFrame nextFrame(String property) { + return new ActivationFrame(property, Optional.empty()); + } + + private

ActivationFrame nextFrame(String property, Function child) { + @SuppressWarnings("unchecked") + final Optional

parent = (Optional

) stk.peek().parent; + return new ActivationFrame(property, parent.map(child)); + } + + @Override + public Activation transformActivation(Activation target) { + stk.push(new ActivationFrame("activation", Optional.of(target))); + try { + return super.transformActivation(target); + } finally { + stk.pop(); + } + } + + @Override + protected Activation.Builder transformActivation_ActiveByDefault( + Supplier creator, Activation.Builder builder, Activation target) { + return builder; + } + + @Override + protected Activation.Builder transformActivation_File( + Supplier creator, Activation.Builder builder, Activation target) { + stk.push(nextFrame("file", Activation::getFile)); + Optional.ofNullable(target.getFile()); + try { + return super.transformActivation_File(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationFile.Builder transformActivationFile_Exists( + Supplier creator, + ActivationFile.Builder builder, + ActivationFile target) { + stk.push(nextFrame("exists")); + try { + return super.transformActivationFile_Exists(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationFile.Builder transformActivationFile_Missing( + Supplier creator, + ActivationFile.Builder builder, + ActivationFile target) { + stk.push(nextFrame("missing")); + try { + return super.transformActivationFile_Missing(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected Activation.Builder transformActivation_Jdk( + Supplier creator, Activation.Builder builder, Activation target) { + stk.push(nextFrame("jdk")); + try { + return super.transformActivation_Jdk(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected Activation.Builder transformActivation_Os( + Supplier creator, Activation.Builder builder, Activation target) { + stk.push(nextFrame("os", Activation::getOs)); + try { + return super.transformActivation_Os(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationOS.Builder transformActivationOS_Arch( + Supplier creator, ActivationOS.Builder builder, ActivationOS target) { + stk.push(nextFrame("arch")); + try { + return super.transformActivationOS_Arch(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationOS.Builder transformActivationOS_Family( + Supplier creator, ActivationOS.Builder builder, ActivationOS target) { + stk.push(nextFrame("family")); + try { + return super.transformActivationOS_Family(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationOS.Builder transformActivationOS_Name( + Supplier creator, ActivationOS.Builder builder, ActivationOS target) { + stk.push(nextFrame("name")); + try { + return super.transformActivationOS_Name(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationOS.Builder transformActivationOS_Version( + Supplier creator, ActivationOS.Builder builder, ActivationOS target) { + stk.push(nextFrame("version")); + try { + return super.transformActivationOS_Version(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected Activation.Builder transformActivation_Packaging( + Supplier creator, Activation.Builder builder, Activation target) { + stk.push(nextFrame("packaging")); + try { + return super.transformActivation_Packaging(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected Activation.Builder transformActivation_Property( + Supplier creator, Activation.Builder builder, Activation target) { + stk.push(nextFrame("property", Activation::getProperty)); + try { + return super.transformActivation_Property(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationProperty.Builder transformActivationProperty_Name( + Supplier creator, + ActivationProperty.Builder builder, + ActivationProperty target) { + stk.push(nextFrame("name")); + try { + return super.transformActivationProperty_Name(creator, builder, target); + } finally { + stk.pop(); + } + } + + @Override + protected ActivationProperty.Builder transformActivationProperty_Value( + Supplier creator, + ActivationProperty.Builder builder, + ActivationProperty target) { + stk.push(nextFrame("value")); + try { + return super.transformActivationProperty_Value(creator, builder, target); + } finally { + stk.pop(); + } + } + } + private final Set validCoordinateIds = new HashSet<>(); private final Set validProfileIds = new HashSet<>(); @@ -288,42 +490,52 @@ public class DefaultModelValidator implements ModelValidator { } private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation, String prefix) { - if (activation == null || activation.getFile() == null) { + if (activation == null) { return; } + final Deque stk = new LinkedList<>(); - ActivationFile file = activation.getFile(); + final Supplier pathSupplier = () -> { + final boolean parallel = false; + return StreamSupport.stream(((Iterable) stk::descendingIterator).spliterator(), parallel) + .map(ActivationFrame::location) + .collect(Collectors.joining(".")); + }; + final Supplier locationSupplier = () -> { + if (stk.size() < 2) { + return null; + } + Iterator f = stk.iterator(); - String path; - String location; + String location = f.next().location; + ActivationFrame parent = f.next(); - if (file.getExists() != null && !file.getExists().isEmpty()) { - path = file.getExists(); - location = "exists"; - } else if (file.getMissing() != null && !file.getMissing().isEmpty()) { - path = file.getMissing(); - location = "missing"; - } else { - return; - } + return parent.parent.map(p -> p.getLocation(location)).orElse(null); + }; + final UnaryOperator transformer = s -> { + if (hasProjectExpression(s)) { + String path = pathSupplier.get(); + Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(s); + while (matcher.find()) { + String propertyName = matcher.group(0); - if (hasProjectExpression(path)) { - Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(path); - while (matcher.find()) { - String propertyName = matcher.group(0); - if (!"${project.basedir}".equals(propertyName)) { + if (path.startsWith("activation.file.") && "${project.basedir}".equals(propertyName)) { + continue; + } addViolation( problems, Severity.WARNING, Version.V30, - prefix + "activation.file." + location, + prefix + path, null, - "Failed to interpolate file location " + path + ": " + propertyName + "Failed to interpolate profile activation property " + s + ": " + propertyName + " expressions are not supported during profile activation.", - file.getLocation(location)); + locationSupplier.get()); } } - } + return s; + }; + new ActivationWalker(stk, transformer).transformActivation(activation); } private void validate20RawPlugins( diff --git a/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java b/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java index 946002f6d2..0fd9041929 100644 --- a/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java +++ b/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java @@ -20,6 +20,8 @@ package org.apache.maven.model.validation; import java.io.InputStream; import java.util.List; +import java.util.Properties; +import java.util.function.UnaryOperator; import org.apache.maven.model.Model; import org.apache.maven.model.building.DefaultModelBuildingRequest; @@ -50,32 +52,40 @@ class DefaultModelValidatorTest { } private SimpleProblemCollector validate(String pom) throws Exception { - return validateEffective(pom, ModelBuildingRequest.VALIDATION_LEVEL_STRICT); + return validateEffective(pom, UnaryOperator.identity()); } private SimpleProblemCollector validateRaw(String pom) throws Exception { - return validateRaw(pom, ModelBuildingRequest.VALIDATION_LEVEL_STRICT); + return validateRaw(pom, UnaryOperator.identity()); } private SimpleProblemCollector validateEffective(String pom, int level) throws Exception { - ModelBuildingRequest request = new DefaultModelBuildingRequest().setValidationLevel(level); + return validateEffective(pom, mbr -> mbr.setValidationLevel(level)); + } + private SimpleProblemCollector validateEffective(String pom, UnaryOperator requestConfigurer) + throws Exception { Model model = read(pom); SimpleProblemCollector problems = new SimpleProblemCollector(model); - validator.validateEffectiveModel(model, request, problems); + validator.validateEffectiveModel(model, requestConfigurer.apply(new DefaultModelBuildingRequest()), problems); return problems; } private SimpleProblemCollector validateRaw(String pom, int level) throws Exception { - ModelBuildingRequest request = new DefaultModelBuildingRequest().setValidationLevel(level); + return validateRaw(pom, mbr -> mbr.setValidationLevel(level)); + } + private SimpleProblemCollector validateRaw(String pom, UnaryOperator requestConfigurer) + throws Exception { Model model = read(pom); SimpleProblemCollector problems = new SimpleProblemCollector(model); + ModelBuildingRequest request = requestConfigurer.apply(new DefaultModelBuildingRequest()); + validator.validateFileModel(model, request, problems); validator.validateRawModel(model, request, problems); @@ -818,24 +828,52 @@ class DefaultModelValidatorTest { @Test void profileActivationWithAllowedExpression() throws Exception { - SimpleProblemCollector result = validateRaw("raw-model/profile-activation-file-with-allowed-expressions.xml"); + SimpleProblemCollector result = validateRaw( + "raw-model/profile-activation-file-with-allowed-expressions.xml", + mbr -> mbr.setUserProperties(new Properties() { + private static final long serialVersionUID = 1L; + + { + setProperty("foo", "foo"); + setProperty("bar", "foo"); + } + })); assertViolations(result, 0, 0, 0); } @Test - void profileActivationWithProjectExpression() throws Exception { + void profileActivationFileWithProjectExpression() throws Exception { SimpleProblemCollector result = validateRaw("raw-model/profile-activation-file-with-project-expressions.xml"); assertViolations(result, 0, 0, 2); assertEquals( "'profiles.profile[exists-project-version].activation.file.exists' " - + "Failed to interpolate file location ${project.version}/test.txt: " + + "Failed to interpolate profile activation property ${project.version}/test.txt: " + "${project.version} expressions are not supported during profile activation.", result.getWarnings().get(0)); assertEquals( "'profiles.profile[missing-project-version].activation.file.missing' " - + "Failed to interpolate file location ${project.version}/test.txt: " + + "Failed to interpolate profile activation property ${project.version}/test.txt: " + + "${project.version} expressions are not supported during profile activation.", + result.getWarnings().get(1)); + } + + @Test + void profileActivationPropertyWithProjectExpression() throws Exception { + SimpleProblemCollector result = + validateRaw("raw-model/profile-activation-property-with-project-expressions.xml"); + assertViolations(result, 0, 0, 2); + + assertEquals( + "'profiles.profile[property-name-project-version].activation.property.name' " + + "Failed to interpolate profile activation property ${project.version}: " + + "${project.version} expressions are not supported during profile activation.", + result.getWarnings().get(0)); + + assertEquals( + "'profiles.profile[property-value-project-version].activation.property.value' " + + "Failed to interpolate profile activation property ${project.version}: " + "${project.version} expressions are not supported during profile activation.", result.getWarnings().get(1)); } diff --git a/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml b/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml index a4beb6238f..72b6747f18 100644 --- a/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml +++ b/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml @@ -60,5 +60,23 @@ under the License. + + dynamic-property-available + + + ${activationProperty} + + + + + + matches-another-property + + + foo + ${bar} + + + diff --git a/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-property-with-project-expressions.xml b/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-property-with-project-expressions.xml new file mode 100644 index 0000000000..8bcf89f66f --- /dev/null +++ b/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-property-with-project-expressions.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + aid + gid + 0.1 + pom + + + + + property-name-project-version + + + ${project.version} + + + + + property-value-project-version + + + project.version + ${project.version} + + + + + +