[MNG-8084] New model builder and resolver provider

This commit is contained in:
Guillaume Nodet 2024-04-11 08:22:55 +02:00
parent 9be08ccef8
commit d075fe7e85
205 changed files with 18913 additions and 1294 deletions

View File

@ -18,6 +18,8 @@
*/ */
package org.apache.maven.api; package org.apache.maven.api;
import java.util.Map;
import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Immutable; import org.apache.maven.api.annotations.Immutable;
import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nonnull;
@ -55,9 +57,9 @@ public interface Packaging extends ExtensibleEnum {
Type type(); Type type();
/** /**
* Returns the binding to use specifically for this packaging. * Returns the binding to use specifically for this packaging keyed by lifecycle id.
* This will be merged to the default packaging definition. * This will be used instead of the default packaging definition.
*/ */
@Nonnull @Nonnull
PluginContainer plugins(); Map<String, PluginContainer> plugins();
} }

View File

@ -0,0 +1,35 @@
/*
* 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.List;
import org.apache.maven.api.Service;
import org.apache.maven.api.model.Model;
public interface ModelBuilder extends Service {
List<String> VALID_MODEL_VERSIONS = List.of("4.0.0", "4.1.0");
ModelBuilderResult build(ModelBuilderRequest request) throws ModelBuilderException;
ModelTransformerContextBuilder newTransformerContextBuilder();
Model buildRawModel(ModelBuilderRequest request);
}

View File

@ -0,0 +1,80 @@
/*
* 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.Collections;
import java.util.List;
import org.apache.maven.api.annotations.Experimental;
/**
* The Exception class throw by the {@link ProjectBuilder} service.
*
* @since 4.0.0
*/
@Experimental
public class ModelBuilderException extends MavenException {
private final ModelBuilderResult result;
/**
* Creates a new exception from the specified interim result and its associated problems.
*
* @param result The interim result, may be {@code null}.
*/
public ModelBuilderException(ModelBuilderResult result) {
super(result.toString());
this.result = result;
}
/**
* Gets the interim result of the model building up to the point where it failed.
*
* @return The interim model building result or {@code null} if not available.
*/
public ModelBuilderResult getResult() {
return result;
}
/**
* Gets the identifier of the POM whose effective model could not be built. The general format of the identifier is
* {@code <groupId>:<artifactId>:<version>} but some of these coordinates may still be unknown at the point the
* exception is thrown so this information is merely meant to assist the user.
*
* @return The identifier of the POM or an empty string if not known, never {@code null}.
*/
public String getModelId() {
if (result == null || result.getModelIds().isEmpty()) {
return "";
}
return result.getModelIds().get(0);
}
/**
* Gets the problems that caused this exception.
*
* @return The problems that caused this exception, never {@code null}.
*/
public List<ModelProblem> getProblems() {
if (result == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(result.getProblems());
}
}

View File

@ -0,0 +1,454 @@
/*
* 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.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.maven.api.Session;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Immutable;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.NotThreadSafe;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.model.Profile;
import static org.apache.maven.api.services.BaseRequest.nonNull;
/**
* Request used to build a {@link org.apache.maven.api.Project} using
* the {@link ProjectBuilder} service.
*
* TODO: add validationLevel, activeProfileIds, inactiveProfileIds, resolveDependencies
*
* @since 4.0.0
*/
@Experimental
@Immutable
public interface ModelBuilderRequest {
/**
* Denotes minimal validation of POMs. This validation level is meant for processing of POMs from repositories
* during metadata retrieval.
*/
int VALIDATION_LEVEL_MINIMAL = 0;
/**
* Denotes validation as performed by Maven 2.0. This validation level is meant as a compatibility mode to allow
* users to migrate their projects.
*/
int VALIDATION_LEVEL_MAVEN_2_0 = 20;
/**
* Denotes validation as performed by Maven 3.0. This validation level is meant for existing projects.
*/
int VALIDATION_LEVEL_MAVEN_3_0 = 30;
/**
* Denotes validation as performed by Maven 3.1. This validation level is meant for existing projects.
*/
int VALIDATION_LEVEL_MAVEN_3_1 = 31;
/**
* Denotes validation as performed by Maven 4.0. This validation level is meant for new projects.
*/
int VALIDATION_LEVEL_MAVEN_4_0 = 40;
/**
* Denotes strict validation as recommended by the current Maven version.
*/
int VALIDATION_LEVEL_STRICT = VALIDATION_LEVEL_MAVEN_4_0;
@Nonnull
Session getSession();
@Nonnull
ModelSource getSource();
int getValidationLevel();
boolean isTwoPhaseBuilding();
boolean isLocationTracking();
/**
* Indicates if the model to be built is a local project or a dependency.
* In case the project is loaded externally from a remote repository (as a dependency or
* even as an external parent), the POM will be parsed in a lenient way. Local POMs
* are parsed more strictly.
*/
boolean isProjectBuild();
/**
* Specifies whether plugin processing should take place for the built model.
* This involves merging plugins specified by the {@link org.apache.maven.api.Packaging},
* configuration expansion (merging configuration defined globally for a given plugin
* using {@link org.apache.maven.api.model.Plugin#getConfiguration()}
* into the configuration for each {@link org.apache.maven.api.model.PluginExecution}.
*/
boolean isProcessPlugins();
/**
* Defines external profiles that may be activated for the given model.
* Those are external profiles usually defined in {@link org.apache.maven.api.settings.Settings#getProfiles()}.
*/
@Nonnull
Collection<Profile> getProfiles();
/**
* List of profile ids that have been explicitly activated by the user.
*/
@Nonnull
List<String> getActiveProfileIds();
/**
* List of profile ids that have been explicitly deactivated by the user.
*/
@Nonnull
List<String> getInactiveProfileIds();
/**
* Provides a map of system properties.
*/
@Nonnull
Map<String, String> getSystemProperties();
/**
* Provides a map of user properties.
* User properties
*/
@Nonnull
Map<String, String> getUserProperties();
ModelResolver getModelResolver();
@Nullable
Object getListener();
@Nullable
ModelBuilderResult getInterimResult();
@Nullable
ModelTransformerContextBuilder getTransformerContextBuilder();
@Nonnull
static ModelBuilderRequest build(@Nonnull ModelBuilderRequest request, @Nonnull ModelSource source) {
return builder(nonNull(request, "request cannot be null"))
.source(nonNull(source, "source cannot be null"))
.build();
}
@Nonnull
static ModelBuilderRequest build(@Nonnull Session session, @Nonnull ModelSource source) {
return builder()
.session(nonNull(session, "session cannot be null"))
.source(nonNull(source, "source cannot be null"))
.build();
}
@Nonnull
static ModelBuilderRequest build(@Nonnull Session session, @Nonnull Path path) {
return builder()
.session(nonNull(session, "session cannot be null"))
.source(ModelSource.fromPath(path))
.build();
}
@Nonnull
static ModelBuilderRequestBuilder builder() {
return new ModelBuilderRequestBuilder();
}
@Nonnull
static ModelBuilderRequestBuilder builder(ModelBuilderRequest request) {
return new ModelBuilderRequestBuilder(request);
}
@NotThreadSafe
class ModelBuilderRequestBuilder {
Session session;
int validationLevel;
boolean locationTracking;
boolean twoPhaseBuilding;
ModelSource source;
boolean projectBuild;
boolean processPlugins = true;
Collection<Profile> profiles;
List<String> activeProfileIds;
List<String> inactiveProfileIds;
Map<String, String> systemProperties;
Map<String, String> userProperties;
ModelResolver modelResolver;
Object listener;
ModelBuilderResult interimResult;
ModelTransformerContextBuilder transformerContextBuilder;
ModelBuilderRequestBuilder() {}
ModelBuilderRequestBuilder(ModelBuilderRequest request) {
this.session = request.getSession();
this.validationLevel = request.getValidationLevel();
this.locationTracking = request.isLocationTracking();
this.twoPhaseBuilding = request.isTwoPhaseBuilding();
this.source = request.getSource();
this.projectBuild = request.isProjectBuild();
this.processPlugins = request.isProcessPlugins();
this.profiles = request.getProfiles();
this.activeProfileIds = request.getActiveProfileIds();
this.inactiveProfileIds = request.getInactiveProfileIds();
this.systemProperties = request.getSystemProperties();
this.userProperties = request.getUserProperties();
this.modelResolver = request.getModelResolver();
this.listener = request.getListener();
this.interimResult = request.getInterimResult();
this.transformerContextBuilder = request.getTransformerContextBuilder();
}
public ModelBuilderRequestBuilder session(Session session) {
this.session = session;
return this;
}
public ModelBuilderRequestBuilder validationLevel(int validationLevel) {
this.validationLevel = validationLevel;
return this;
}
public ModelBuilderRequestBuilder twoPhaseBuilding(boolean twoPhaseBuilding) {
this.twoPhaseBuilding = twoPhaseBuilding;
return this;
}
public ModelBuilderRequestBuilder locationTracking(boolean locationTracking) {
this.locationTracking = locationTracking;
return this;
}
public ModelBuilderRequestBuilder source(ModelSource source) {
this.source = source;
return this;
}
public ModelBuilderRequestBuilder projectBuild(boolean projectBuild) {
this.projectBuild = projectBuild;
return this;
}
public ModelBuilderRequestBuilder processPlugins(boolean processPlugins) {
this.processPlugins = processPlugins;
return this;
}
public ModelBuilderRequestBuilder profiles(List<Profile> profiles) {
this.profiles = profiles;
return this;
}
public ModelBuilderRequestBuilder activeProfileIds(List<String> activeProfileIds) {
this.activeProfileIds = activeProfileIds;
return this;
}
public ModelBuilderRequestBuilder inactiveProfileIds(List<String> inactiveProfileIds) {
this.inactiveProfileIds = inactiveProfileIds;
return this;
}
public ModelBuilderRequestBuilder systemProperties(Map<String, String> systemProperties) {
this.systemProperties = systemProperties;
return this;
}
public ModelBuilderRequestBuilder userProperties(Map<String, String> userProperties) {
this.userProperties = userProperties;
return this;
}
public ModelBuilderRequestBuilder modelResolver(ModelResolver modelResolver) {
this.modelResolver = modelResolver;
return this;
}
public ModelBuilderRequestBuilder listener(Object listener) {
this.listener = listener;
return this;
}
public ModelBuilderRequestBuilder interimResult(ModelBuilderResult interimResult) {
this.interimResult = interimResult;
return this;
}
public ModelBuilderRequestBuilder transformerContextBuilder(
ModelTransformerContextBuilder transformerContextBuilder) {
this.transformerContextBuilder = transformerContextBuilder;
return this;
}
public ModelBuilderRequest build() {
return new DefaultModelBuilderRequest(
session,
validationLevel,
locationTracking,
twoPhaseBuilding,
source,
projectBuild,
processPlugins,
profiles,
activeProfileIds,
inactiveProfileIds,
systemProperties,
userProperties,
modelResolver,
listener,
interimResult,
transformerContextBuilder);
}
private static class DefaultModelBuilderRequest extends BaseRequest implements ModelBuilderRequest {
private final int validationLevel;
private final boolean locationTracking;
private final boolean twoPhaseBuilding;
private final ModelSource source;
private final boolean projectBuild;
private final boolean processPlugins;
private final Collection<Profile> profiles;
private final List<String> activeProfileIds;
private final List<String> inactiveProfileIds;
private final Map<String, String> systemProperties;
private final Map<String, String> userProperties;
private final ModelResolver modelResolver;
private final Object listener;
private final ModelBuilderResult interimResult;
private final ModelTransformerContextBuilder transformerContextBuilder;
@SuppressWarnings("checkstyle:ParameterNumber")
DefaultModelBuilderRequest(
@Nonnull Session session,
int validationLevel,
boolean locationTracking,
boolean twoPhaseBuilding,
@Nonnull ModelSource source,
boolean projectBuild,
boolean processPlugins,
Collection<Profile> profiles,
List<String> activeProfileIds,
List<String> inactiveProfileIds,
Map<String, String> systemProperties,
Map<String, String> userProperties,
ModelResolver modelResolver,
Object listener,
ModelBuilderResult interimResult,
ModelTransformerContextBuilder transformerContextBuilder) {
super(session);
this.validationLevel = validationLevel;
this.locationTracking = locationTracking;
this.twoPhaseBuilding = twoPhaseBuilding;
this.source = source;
this.projectBuild = projectBuild;
this.processPlugins = processPlugins;
this.profiles = profiles != null ? List.copyOf(profiles) : List.of();
this.activeProfileIds = activeProfileIds != null ? List.copyOf(activeProfileIds) : List.of();
this.inactiveProfileIds = inactiveProfileIds != null ? List.copyOf(inactiveProfileIds) : List.of();
this.systemProperties =
systemProperties != null ? Map.copyOf(systemProperties) : session.getSystemProperties();
this.userProperties = userProperties != null ? Map.copyOf(userProperties) : session.getUserProperties();
this.modelResolver = modelResolver;
this.listener = listener;
this.interimResult = interimResult;
this.transformerContextBuilder = transformerContextBuilder;
}
@Override
public int getValidationLevel() {
return validationLevel;
}
@Override
public boolean isTwoPhaseBuilding() {
return twoPhaseBuilding;
}
@Override
public boolean isLocationTracking() {
return locationTracking;
}
@Nonnull
@Override
public ModelSource getSource() {
return source;
}
public boolean isProjectBuild() {
return projectBuild;
}
@Override
public boolean isProcessPlugins() {
return processPlugins;
}
@Override
public Collection<Profile> getProfiles() {
return profiles;
}
@Override
public List<String> getActiveProfileIds() {
return activeProfileIds;
}
@Override
public List<String> getInactiveProfileIds() {
return inactiveProfileIds;
}
@Override
public Map<String, String> getSystemProperties() {
return systemProperties;
}
@Override
public Map<String, String> getUserProperties() {
return userProperties;
}
@Override
public ModelResolver getModelResolver() {
return modelResolver;
}
public Object getListener() {
return listener;
}
@Override
public ModelBuilderResult getInterimResult() {
return interimResult;
}
public ModelTransformerContextBuilder getTransformerContextBuilder() {
return transformerContextBuilder;
}
}
}
}

View File

@ -0,0 +1,125 @@
/*
* 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.List;
import java.util.Optional;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Profile;
/**
* Result of a project build call.
*
* @since 4.0.0
*/
@Experimental
public interface ModelBuilderResult {
/**
* Gets the sequence of model identifiers that denote the lineage of models from which the effective model was
* constructed. Model identifiers have the form {@code <groupId>:<artifactId>:<version>}. The first identifier from
* the list denotes the model on which the model builder was originally invoked. The last identifier will always be
* an empty string that by definition denotes the super POM.
*
* @return The model identifiers from the lineage of models, never {@code null}.
*/
@Nonnull
List<String> getModelIds();
/**
* Gets the file model.
*
* @return the file model, never {@code null}.
*/
@Nonnull
Model getFileModel();
/**
* Returns the file model + profile injection.
*
* @return the activated file model, never {@code null}.
*/
@Nonnull
Model getActivatedFileModel();
/**
* Gets the file model + build pom transformation, without inheritance nor interpolation.
*
* @return The raw model, never {@code null}.
*/
@Nonnull
Model getRawModel();
/**
* Gets the assembled model with inheritance, interpolation and profile injection.
*
* @return The assembled model, never {@code null}.
*/
@Nonnull
Model getEffectiveModel();
/**
* Gets the specified raw model as it was read from a model source. Apart from basic validation, a raw model has not
* undergone any updates by the model builder, e.g. reflects neither inheritance nor interpolation. The model
* identifier should be from the collection obtained by {@link #getModelIds()}. As a special case, an empty string
* can be used as the identifier for the super POM.
*
* @param modelId The identifier of the desired raw model, must not be {@code null}.
* @return The raw model or {@code null} if the specified model id does not refer to a known model.
*/
@Nonnull
Optional<Model> getRawModel(@Nonnull String modelId);
/**
* Gets the profiles from the specified model that were active during model building. The model identifier should be
* from the collection obtained by {@link #getModelIds()}. As a special case, an empty string can be used as the
* identifier for the super POM.
*
* @param modelId The identifier of the model whose active profiles should be retrieved, must not be {@code null}.
* @return The active profiles of the model or an empty list if the specified model id does
* not refer to a known model or has no active profiles.
*/
@Nonnull
List<Profile> getActivePomProfiles(@Nonnull String modelId);
/**
* Gets the external profiles that were active during model building. External profiles are those that were
* contributed by {@link ModelBuilderRequest#getProfiles()}.
*
* @return The active external profiles or an empty list if none, never {@code null}.
*/
@Nonnull
List<Profile> getActiveExternalProfiles();
/**
* Gets the problems that were encountered during the project building.
*
* @return the problems that were encountered during the project building, can be empty but never {@code null}
*/
@Nonnull
List<ModelProblem> getProblems();
/**
* Creates a human readable representation of these errors.
*/
String toString();
}

View File

@ -0,0 +1,57 @@
/*
* 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;
/**
* Describes a problem that was encountered during model building. A problem can either be an exception that was thrown
* or a simple string message. In addition, a problem carries a hint about its source, e.g. the POM file that exhibits
* the problem.
*
*/
public interface ModelProblem extends BuilderProblem {
/**
* Version
*/
enum Version {
// based on ModeBuildingResult.validationLevel
BASE,
V20,
V30,
V31,
V40
}
/**
* Gets the identifier of the model from which the problem originated. While the general form of this identifier is
* <code>groupId:artifactId:version</code> the returned identifier need not be complete. The identifier is derived
* from the information that is available at the point the problem occurs and as such merely serves as a best effort
* to provide information to the user to track the problem back to its origin.
*
* @return The identifier of the model from which the problem originated or an empty string if unknown, never
* {@code null}.
*/
String getModelId();
/**
* Gets the applicable maven version/validation level of this problem
* @return The version, never {@code null}.
*/
Version getVersion();
}

View File

@ -0,0 +1,67 @@
/*
* 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.List;
import org.apache.maven.api.model.InputLocation;
/**
* Collects problems that are encountered during model building. The primary purpose of this component is to account for
* the fact that the problem reporter has/should not have information about the calling context and hence cannot provide
* an expressive source hint for the model problem. Instead, the source hint is configured by the model builder before
* it delegates to other components that potentially encounter problems. Then, the problem reporter can focus on
* providing a simple error message, leaving the donkey work of creating a nice model problem to this component.
*
*/
public interface ModelProblemCollector {
/**
* The collected problems.
* @return a list of model problems encountered, never {@code null}
*/
List<ModelProblem> getProblems();
boolean hasErrors();
boolean hasFatalErrors();
default void add(BuilderProblem.Severity severity, ModelProblem.Version version, String message) {
add(severity, version, message, null, null);
}
default void add(
BuilderProblem.Severity severity, ModelProblem.Version version, String message, InputLocation location) {
add(severity, version, message, location, null);
}
default void add(
BuilderProblem.Severity severity, ModelProblem.Version version, String message, Exception exception) {
add(severity, version, message, null, exception);
}
void add(
BuilderProblem.Severity severity,
ModelProblem.Version version,
String message,
InputLocation location,
Exception exception);
void add(ModelProblem problem);
}

View File

@ -0,0 +1,125 @@
/*
* 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.concurrent.atomic.AtomicReference;
import org.apache.maven.api.Service;
import org.apache.maven.api.Session;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.model.Dependency;
import org.apache.maven.api.model.Parent;
import org.apache.maven.api.model.Repository;
/**
* Resolves a POM from its coordinates. During the build process, the
* {@link org.apache.maven.api.services.ModelBuilder} will add any relevant repositories to the model resolver. In
* other words, the model resolver is stateful and should not be reused across multiple model building requests.
*
*/
public interface ModelResolver extends Service {
/**
* Tries to resolve the POM for the specified coordinates.
*
* @param groupId The group identifier of the POM, must not be {@code null}.
* @param artifactId The artifact identifier of the POM, must not be {@code null}.
* @param version The version of the POM, must not be {@code null}.
* @return The source of the requested POM, never {@code null}.
* @throws ModelResolverException If the POM could not be resolved from any configured repository.
*/
@Nonnull
ModelSource resolveModel(
@Nonnull Session session, @Nonnull String groupId, @Nonnull String artifactId, @Nonnull String version)
throws ModelResolverException;
/**
* Tries to resolve the POM for the specified parent coordinates possibly updating {@code parent}.
* <p>
* Unlike the {@link #resolveModel(Session, String, String, String)} method, this method
* supports version ranges and updates the given {@code parent} instance to match the returned {@code ModelSource}.
* If {@code parent} declares a version range, the version corresponding to the returned {@code ModelSource} will
* be set on the given {@code parent}.
* </p>
*
* @param parent The parent coordinates to resolve, must not be {@code null}.
*
* @return The source of the requested POM, never {@code null}.
*
* @throws ModelResolverException If the POM could not be resolved from any configured repository.
*/
@Nonnull
ModelSource resolveModel(
@Nonnull Session session, @Nonnull Parent parent, @Nonnull AtomicReference<Parent> modified)
throws ModelResolverException;
/**
* Tries to resolve the POM for the specified dependency coordinates possibly updating {@code dependency}.
* <p>
* Unlike the {@link #resolveModel(Session, String, String, String)} method, this method
* supports version ranges and updates the given {@code dependency} instance to match the returned
* {@code ModelSource}. If {@code dependency} declares a version range, the version corresponding to the returned
* {@code ModelSource} will be set on the given {@code dependency}.
* </p>
*
* @param dependency The dependency coordinates to resolve, must not be {@code null}.
*
* @return The source of the requested POM, never {@code null}.
*
* @throws ModelResolverException If the POM could not be resolved from any configured repository.
*
* @see Dependency#clone()
*/
@Nonnull
ModelSource resolveModel(
@Nonnull Session session, @Nonnull Dependency dependency, @Nonnull AtomicReference<Dependency> modified)
throws ModelResolverException;
/**
* Adds a repository to use for subsequent resolution requests. The order in which repositories are added matters,
* repositories that were added first should also be searched first. When multiple repositories with the same
* identifier are added, only the first repository being added will be used.
*
* @param repository The repository to add to the internal search chain, must not be {@code null}.
* @throws ModelResolverException If the repository could not be added (e.g. due to invalid URL or layout).
*/
void addRepository(@Nonnull Session session, Repository repository) throws ModelResolverException;
/**
* Adds a repository to use for subsequent resolution requests. The order in which repositories are added matters,
* repositories that were added first should also be searched first. When multiple repositories with the same
* identifier are added, then the value of the replace argument determines the behaviour.
*
* If replace is false then any existing repository with the same Id will remain in use. If replace
* is true the new repository replaces the original.
*
* @param repository The repository to add to the internal search chain, must not be {@code null}.
* @throws ModelResolverException If the repository could not be added (e.g. due to invalid URL or layout).
*/
void addRepository(@Nonnull Session session, Repository repository, boolean replace) throws ModelResolverException;
/**
* Clones this resolver for usage in a forked resolution process. In general, implementors need not provide a deep
* clone. The only requirement is that invocations of {@link #addRepository(Session, Repository)} on the clone do not affect
* the state of the original resolver and vice versa.
*
* @return The cloned resolver, never {@code null}.
*/
ModelResolver newCopy();
}

View File

@ -0,0 +1,114 @@
/*
* 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;
/**
* Signals an error when resolving the path to an external model.
*
*/
public class ModelResolverException extends MavenException {
/**
* The group id of the unresolvable model.
*/
private final String groupId;
/**
* The artifact id of the unresolvable model.
*/
private final String artifactId;
/**
* The version of the unresolvable model.
*/
private final String version;
/**
* Creates a new exception with specified detail message and cause.
*
* @param message The detail message, may be {@code null}.
* @param groupId The group id of the unresolvable model, may be {@code null}.
* @param artifactId The artifact id of the unresolvable model, may be {@code null}.
* @param version The version of the unresolvable model, may be {@code null}.
* @param cause The cause, may be {@code null}.
*/
public ModelResolverException(String message, String groupId, String artifactId, String version, Throwable cause) {
super(message, cause);
this.groupId = (groupId != null) ? groupId : "";
this.artifactId = (artifactId != null) ? artifactId : "";
this.version = (version != null) ? version : "";
}
/**
* Creates a new exception with specified detail message.
*
* @param message The detail message, may be {@code null}.
* @param groupId The group id of the unresolvable model, may be {@code null}.
* @param artifactId The artifact id of the unresolvable model, may be {@code null}.
* @param version The version of the unresolvable model, may be {@code null}.
*/
public ModelResolverException(String message, String groupId, String artifactId, String version) {
super(message);
this.groupId = (groupId != null) ? groupId : "";
this.artifactId = (artifactId != null) ? artifactId : "";
this.version = (version != null) ? version : "";
}
/**
* Creates a new exception with specified cause
*
* @param cause
* @param groupId
* @param artifactId
* @param version
*/
public ModelResolverException(Throwable cause, String groupId, String artifactId, String version) {
super(cause);
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
}
/**
* Gets the group id of the unresolvable model.
*
* @return The group id of the unresolvable model, can be empty but never {@code null}.
*/
public String getGroupId() {
return groupId;
}
/**
* Gets the artifact id of the unresolvable model.
*
* @return The artifact id of the unresolvable model, can be empty but never {@code null}.
*/
public String getArtifactId() {
return artifactId;
}
/**
* Gets the version of the unresolvable model.
*
* @return The version of the unresolvable model, can be empty but never {@code null}.
*/
public String getVersion() {
return version;
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.nio.file.Path;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
import static org.apache.maven.api.services.BaseRequest.nonNull;
/**
* A Source specific to load POMs. The {@link #resolve(ModelLocator, String)} method
* will be used to find POMs for children modules.
*
* @since 4.0.0
*/
public interface ModelSource extends Source {
interface ModelLocator {
/**
* Returns the file containing the pom or null if a pom can not be
* found at the given file or in the given directory.
*
* @since 4.0.0
*/
@Nullable
Path locateExistingPom(@Nonnull Path project);
}
@Nullable
ModelSource resolve(@Nonnull ModelLocator modelLocator, @Nonnull String relative);
@Nonnull
static ModelSource fromPath(@Nonnull Path path) {
return fromPath(path, null);
}
@Nonnull
static ModelSource fromPath(@Nonnull Path path, @Nullable String location) {
return new PathSource(nonNull(path, "path cannot be null"), location);
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.nio.file.Path;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.model.Model;
/**
* The ModelTransformer is a way to transform the local pom while streaming the input.
*
* The {@link #transform(ModelTransformerContext, Model, Path)} method uses a Path on purpose, to ensure the
* local pom is the original source.
*
* @since 4.0.0
*/
public interface ModelTransformer {
/**
* @param context the context, cannot be null
* @param model the model to transform
* @param path the pom file, cannot be null
* @throws ModelTransformerException if the transformation fails
*/
@Nonnull
Model transform(@Nonnull ModelTransformerContext context, @Nonnull Model model, @Nonnull Path path)
throws ModelTransformerException;
}

View File

@ -0,0 +1,66 @@
/*
* 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.nio.file.Path;
import org.apache.maven.api.model.Model;
/**
* Context used to transform a pom file.
*
* @since 4.0.0
*/
public interface ModelTransformerContext {
/**
* Key to get the TransformerContext from the SessionData
*/
Object KEY = ModelTransformerContext.class;
/**
* Get the value of the Maven user property.
*/
String getUserProperty(String key);
/**
* Get the model based on the path when resolving the parent based on relativePath.
*
* @param from the requiring model
* @param pomFile the path to the pomFile
* @return the model, otherwise {@code null}
*/
Model getRawModel(Path from, Path pomFile);
/**
* Get the model from the reactor based on the groupId and artifactId when resolving reactor dependencies.
*
* @param from the requiring model
* @param groupId the groupId
* @param artifactId the artifactId
* @return the model, otherwise {@code null}
* @throws IllegalStateException if multiple versions of the same GA are part of the reactor
*/
Model getRawModel(Path from, String groupId, String artifactId);
/**
* Locate the POM file inside the given directory.
*/
Path locate(Path path);
}

View File

@ -0,0 +1,43 @@
/*
* 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;
/**
* The transformerContextBuilder is responsible for initializing the TransformerContext.
* In case rawModels are missing, it could do new buildingRequests on the ModelBuilder.
*
* @since 4.0.0
*/
public interface ModelTransformerContextBuilder {
/**
* This method is used to initialize the TransformerContext
*
* @param request the modelBuildingRequest
* @param problems the problemCollector
* @return the mutable transformerContext
*/
ModelTransformerContext initialize(ModelBuilderRequest request, ModelProblemCollector problems);
/**
* The immutable transformerContext, can be used after the buildplan is finished.
*
* @return the immutable transformerContext
*/
ModelTransformerContext build();
}

View File

@ -0,0 +1,34 @@
/*
* 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;
/**
*
* @since 4.0.0
*/
public class ModelTransformerException extends MavenException {
public ModelTransformerException(Exception e) {
super(e);
}
public ModelTransformerException(String message, Throwable exception) {
super(message, exception);
}
}

View File

@ -18,17 +18,25 @@
*/ */
package org.apache.maven.api.services; package org.apache.maven.api.services;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Objects;
class PathSource implements Source { class PathSource implements ModelSource {
private final Path path; private final Path path;
private final String location;
PathSource(Path path) { PathSource(Path path) {
this(path, null);
}
PathSource(Path path, String location) {
this.path = path; this.path = path;
this.location = location != null ? location : path.toString();
} }
@Override @Override
@ -43,11 +51,32 @@ class PathSource implements Source {
@Override @Override
public String getLocation() { public String getLocation() {
return path.toString(); return location;
} }
@Override @Override
public Source resolve(String relative) { public Source resolve(String relative) {
return new PathSource(path.resolve(relative)); return new PathSource(path.resolve(relative));
} }
@Override
public ModelSource resolve(ModelLocator locator, String relative) {
String norm = relative.replace('\\', File.separatorChar).replace('/', File.separatorChar);
Path path = getPath().getParent().resolve(norm);
Path relatedPom = locator.locateExistingPom(path);
if (relatedPom != null) {
return new PathSource(relatedPom.normalize(), null);
}
return null;
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof PathSource ps && Objects.equals(path, ps.path);
}
@Override
public int hashCode() {
return Objects.hash(path);
}
} }

View File

@ -87,7 +87,8 @@ public interface Source {
* @param relative is the path of the requested source relative to this source * @param relative is the path of the requested source relative to this source
* @return related source or <code>null</code> if no such source * @return related source or <code>null</code> if no such source
*/ */
Source resolve(String relative); @Nullable
Source resolve(@Nonnull String relative);
/** /**
* Creates a Source for the following Path * Creates a Source for the following Path

View File

@ -39,5 +39,8 @@ public interface TypeRegistry extends ExtensibleEnumRegistry<Type> {
* @return the type * @return the type
*/ */
@Nonnull @Nonnull
Type require(@Nonnull String id); @Override
default Type require(@Nonnull String id) {
return lookup(id).orElseThrow(() -> new IllegalArgumentException("Unknown extensible enum value '" + id + "'"));
}
} }

View File

@ -33,7 +33,7 @@ under the License.
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-xml-impl</artifactId> <artifactId>maven-api-xml</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -39,6 +39,11 @@ import org.apache.maven.api.services.Source;
@Consumer @Consumer
public interface ModelParser extends SpiService { public interface ModelParser extends SpiService {
/**
* Option that can be specified in the options map. The value should be a Boolean.
*/
String STRICT = "strict";
/** /**
* Locates the pom in the given directory. * Locates the pom in the given directory.
* *

View File

@ -34,6 +34,14 @@ under the License.
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-api-core</artifactId> <artifactId>maven-api-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-spi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-metadata</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-di</artifactId> <artifactId>maven-di</artifactId>
@ -52,7 +60,15 @@ under the License.
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-resolver-provider</artifactId> <artifactId>maven-xml-impl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-impl</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-interpolation</artifactId>
</dependency> </dependency>
<dependency> <dependency>
@ -60,6 +76,21 @@ under the License.
<artifactId>mockito-junit-jupiter</artifactId> <artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-connector-basic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-transport-file</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-transport-apache</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -123,6 +154,59 @@ under the License.
</params> </params>
</configuration> </configuration>
</execution> </execution>
<execution>
<id>velocity-metadata</id>
<goals>
<goal>velocity</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<version>1.1.0</version>
<basedir>${project.basedir}/../api/maven-api-metadata</basedir>
<velocityBasedir>${project.basedir}/../src/mdo</velocityBasedir>
<models>
<model>src/main/mdo/metadata.mdo</model>
</models>
<templates>
<template>reader-stax.vm</template>
<template>writer-stax.vm</template>
</templates>
<params>
<param>packageModelV4=org.apache.maven.api.metadata</param>
<param>packageToolV4=org.apache.maven.metadata.v4</param>
</params>
</configuration>
</execution>
<execution>
<id>model-v4</id>
<goals>
<goal>velocity</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<version>4.1.0</version>
<basedir>${project.basedir}/../api/maven-api-model</basedir>
<velocityBasedir>${project.basedir}/../src/mdo</velocityBasedir>
<models>
<model>src/main/mdo/maven.mdo</model>
</models>
<templates>
<template>merger.vm</template>
<template>transformer.vm</template>
<template>reader-stax.vm</template>
<template>writer-stax.vm</template>
<template>model-version.vm</template>
</templates>
<params>
<param>forcedIOModelVersion=4.0.0</param>
<param>packageModelV3=org.apache.maven.model</param>
<param>packageModelV4=org.apache.maven.api.model</param>
<param>packageToolV4=org.apache.maven.model.v4</param>
<param>isMavenModel=true</param>
<param>minimalVersion=4.0.0</param>
</params>
</configuration>
</execution>
<execution> <execution>
<id>modello-site-docs</id> <id>modello-site-docs</id>
<phase>none</phase> <phase>none</phase>

View File

@ -0,0 +1,47 @@
/*
* 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.model;
import java.util.List;
import org.apache.maven.api.model.DependencyManagement;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Handles the import of dependency management from other models into the target model.
*
*/
public interface DependencyManagementImporter {
/**
* Imports the specified dependency management sections into the given target model.
*
* @param target The model into which to import the dependency management section, must not be <code>null</code>.
* @param sources The dependency management sections to import, may be <code>null</code>.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model importManagement(
Model target,
List<? extends DependencyManagement> sources,
ModelBuilderRequest request,
ModelProblemCollector problems);
}

View File

@ -0,0 +1,40 @@
/*
* 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.model;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Handles injection of dependency management into the model.
*
*/
public interface DependencyManagementInjector {
/**
* Merges default values from the dependency management section of the given model into itself.
*
* @param model The model into which to merge the values specified by its dependency management sections, must not
* be <code>null</code>.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
}

View File

@ -0,0 +1,44 @@
/*
* 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.model;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Handles inheritance of model values.
*
*/
public interface InheritanceAssembler {
/**
* Merges values from the specified parent model into the given child model. Implementations are expected to keep
* parent and child completely decoupled by injecting deep copies of objects into the child rather than the original
* objects from the parent.
*
* @param child The child model into which to merge the values inherited from the parent, must not be
* <code>null</code>.
* @param parent The (read-only) parent model from which to inherit the values, may be <code>null</code>.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model assembleModelInheritance(
Model child, Model parent, ModelBuilderRequest request, ModelProblemCollector problems);
}

View File

@ -0,0 +1,42 @@
/*
* 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.model;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Handles injection of plugin executions induced by the lifecycle bindings for a packaging.
*
*/
public interface LifecycleBindingsInjector {
/**
* Injects plugin executions induced by lifecycle bindings into the specified model. The model has already undergone
* injection of plugin management so any plugins that are injected by lifecycle bindings and are not already present
* in the model's plugin section need to be subjected to the model's plugin management.
*
* @param model The model into which to inject the default plugin executions for its packaging, must not be
* <code>null</code>.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectLifecycleBindings(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
}

View File

@ -0,0 +1,55 @@
/*
* 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.model;
import java.util.function.Consumer;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Holds data relevant for a model building event.
*
*/
public interface ModelBuildingEvent {
/**
* Gets the model being built. The precise state of this model depends on the event being fired.
*
* @return The model being built, never {@code null}.
*/
Model model();
Consumer<Model> update();
/**
* Gets the model building request being processed.
*
* @return The model building request being processed, never {@code null}.
*/
ModelBuilderRequest request();
/**
* Gets the container used to collect problems that were encountered while processing the event.
*
* @return The container used to collect problems that were encountered, never {@code null}.
*/
ModelProblemCollector problems();
}

View File

@ -0,0 +1,33 @@
/*
* 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.model;
/**
* Defines events that the model builder fires during construction of the effective model. When a listener encounters
* errors while processing the event, it can report these problems via {@link ModelBuildingEvent#problems()}.
*/
public interface ModelBuildingListener {
/**
* Notifies the listener that the model has been constructed to the extent where build extensions can be processed.
*
* @param event The details about the event.
*/
default void buildExtensionsAssembled(ModelBuildingEvent event) {}
}

View File

@ -0,0 +1,38 @@
/*
* 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.model;
import java.util.function.Supplier;
import org.apache.maven.api.services.Source;
/**
* Caches auxiliary data used during model building like already processed raw/effective models. The data in the cache
* is meant for exclusive consumption by the model builder and is opaque to the cache implementation. The cache key is
* formed by a combination of group id, artifact id, version and tag. The first three components generally refer to the
* identity of a model. The tag allows for further classification of the associated data on the sole discretion of the
* model builder.
*
*/
public interface ModelCache {
<T> T computeIfAbsent(String groupId, String artifactId, String version, String tag, Supplier<T> data);
<T> T computeIfAbsent(Source path, String tag, Supplier<T> data);
}

View File

@ -0,0 +1,48 @@
/*
* 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.model;
import java.nio.file.Path;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Replaces expressions of the form <code>${token}</code> with their effective values. Effective values are basically
* calculated from the elements of the model itself and the execution properties from the building request.
*
*/
public interface ModelInterpolator {
/**
* Interpolates expressions in the specified model. Note that implementations are free to either interpolate the
* provided model directly or to create a clone of the model and interpolate the clone. Callers should always use
* the returned model and must not rely on the input model being updated.
*
* @param model The model to interpolate, must not be {@code null}.
* @param projectDir The project directory, may be {@code null} if the model does not belong to a local project but
* to some artifact's metadata.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
* @return The interpolated model, never {@code null}.
* @since 4.0.0
*/
Model interpolateModel(Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems);
}

View File

@ -0,0 +1,50 @@
/*
* 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.model;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Handles normalization of a model. In this context, normalization is the process of producing a canonical
* representation for models that physically look different but are semantically equivalent.
*
*/
public interface ModelNormalizer {
/**
* Merges duplicate elements like multiple declarations of the same build plugin in the specified model.
*
* @param model The model whose duplicate elements should be merged, must not be {@code null}.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model mergeDuplicates(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
/**
* Sets default values in the specified model that for technical reasons cannot be set directly in the Modello
* definition.
*
* @param model The model in which to set the default values, must not be {@code null}.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectDefaultValues(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
}

View File

@ -0,0 +1,42 @@
/*
* 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.model;
import java.nio.file.Path;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
/**
* Resolves relative paths of a model against a specific base directory.
*
*/
public interface ModelPathTranslator {
/**
* Resolves the well-known paths of the specified model against the given base directory. Paths within plugin
* configuration are not processed.
*
* @param model The model whose paths should be resolved, may be {@code null}.
* @param basedir The base directory to resolve relative paths against, may be {@code null}.
* @param request The model building request that holds further settings, must not be {@code null}.
* @since 4.0.0
*/
Model alignToBaseDirectory(Model model, Path basedir, ModelBuilderRequest request);
}

View File

@ -0,0 +1,52 @@
/*
* 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.model;
import java.io.IOException;
import java.nio.file.Path;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.xml.XmlReaderException;
import org.apache.maven.api.services.xml.XmlReaderRequest;
/**
* ModelProcessor
*/
public interface ModelProcessor {
/**
* Returns the file containing the pom to be parsed or null if a pom can not be found
* at the given file or in the given directory.
*/
@Nullable
Path locateExistingPom(@Nonnull Path project);
/**
* Reads the model from the specified byte stream. The stream will be automatically closed before the method
* returns.
*
* @param request The reader request to deserialize the model, must not be {@code null}.
* @return The deserialized model, never {@code null}.
* @throws IOException If the model could not be deserialized.
* @throws XmlReaderException If the input format could not be parsed.
*/
Model read(XmlReaderRequest request) throws IOException, XmlReaderException;
}

View File

@ -0,0 +1,38 @@
/*
* 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.model;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
/**
* Normalizes URLs to remove the ugly parent references "../" that got potentially inserted by URL adjustment during
* model inheritance.
*
*/
public interface ModelUrlNormalizer {
/**
* Normalizes the well-known URLs of the specified model.
*
* @param model The model whose URLs should be normalized, may be {@code null}.
* @param request The model building request that holds further settings, must not be {@code null}.
*/
Model normalize(Model model, ModelBuilderRequest request);
}

View File

@ -0,0 +1,61 @@
/*
* 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.model;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Checks the model for missing or invalid values.
*
*/
public interface ModelValidator {
/**
* Checks the specified file model for missing or invalid values. This model is directly created from the POM
* file and has not been subjected to inheritance, interpolation or profile/default injection.
*
* @param model The model to validate, must not be {@code null}.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
default void validateFileModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
// do nothing
}
/**
* Checks the specified (raw) model for missing or invalid values. The raw model is the file model + buildpom filter
* transformation and has not been subjected to inheritance, interpolation or profile/default injection.
*
* @param model The model to validate, must not be {@code null}.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
void validateRawModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
/**
* Checks the specified (effective) model for missing or invalid values. The effective model is fully assembled and
* has undergone inheritance, interpolation and other model operations.
*
* @param model The model to validate, must not be {@code null}.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
void validateEffectiveModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
}

View File

@ -0,0 +1,70 @@
/*
* 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.model;
import org.apache.maven.api.Version;
import org.apache.maven.api.VersionConstraint;
import org.apache.maven.api.VersionRange;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.services.VersionParserException;
/**
* Model builder specific version parser. It is intentionally not
* {@link org.apache.maven.api.services.VersionParser} as this is not a service,
* but at Maven runtime it MAY actually use that service.
*
* @since 4.0.0
*/
public interface ModelVersionParser {
/**
* Parses the specified version string, for example "1.0".
*
* @param version the version string to parse, must not be {@code null}
* @return the parsed version, never {@code null}
* @throws VersionParserException if the string violates the syntax rules of this scheme
*/
@Nonnull
Version parseVersion(@Nonnull String version);
/**
* Parses the specified version range specification, for example "[1.0,2.0)".
*
* @param range the range specification to parse, must not be {@code null}
* @return the parsed version range, never {@code null}
* @throws VersionParserException if the range specification violates the syntax rules of this scheme
*/
@Nonnull
VersionRange parseVersionRange(@Nonnull String range);
/**
* Parses the specified version constraint specification, for example "1.0" or "[1.0,2.0)".
*
* @param constraint the range specification to parse, must not be {@code null}
* @return the parsed version constraint, never {@code null}
* @throws VersionParserException if the range specification violates the syntax rules of this scheme
*/
@Nonnull
VersionConstraint parseVersionConstraint(@Nonnull String constraint);
/**
* Checks whether a given artifact version is considered a {@code SNAPSHOT} or not.
*/
boolean isSnapshot(@Nonnull String version);
}

View File

@ -0,0 +1,44 @@
/*
* 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.model;
import java.util.Properties;
import org.apache.maven.api.services.ModelBuilderRequest;
/**
* Allows a fixed set of properties that are valid inside a version and that could be overwritten for example on the
* commandline
*/
public interface ModelVersionProcessor {
/**
* @param property the property to check
* @return <code>true</code> if this is a valid property for this processor
*/
boolean isValidProperty(String property);
/**
* This method is responsible for examining the request and possibly overwrite of the valid properties in the model
*
* @param modelProperties
* @param request
*/
void overwriteModelProperties(Properties modelProperties, ModelBuilderRequest request);
}

View File

@ -0,0 +1,40 @@
/*
* 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.model;
import java.nio.file.Path;
/**
* Resolves relative paths against a specific base directory.
*
*/
public interface PathTranslator {
/**
* Resolves the specified path against the given base directory. The resolved path will be absolute and uses the
* platform-specific file separator if a base directory is given. Otherwise, the input path will be returned
* unaltered.
*
* @param path The path to resolve, may be {@code null}.
* @param basedir The base directory to resolve relative paths against, may be {@code null}.
* @return The resolved path or {@code null} if the input path was {@code null}.
* @since 4.0.0
*/
String alignToBaseDirectory(String path, Path basedir);
}

View File

@ -0,0 +1,39 @@
/*
* 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.model;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Handles expansion of general build plugin configuration into individual executions.
*
*/
public interface PluginConfigurationExpander {
/**
* Merges values from general build plugin configuration into the individual plugin executions of the given model.
*
* @param model The model whose build plugin configuration should be expanded, must not be <code>null</code>.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model expandPluginConfiguration(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
}

View File

@ -0,0 +1,40 @@
/*
* 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.model;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Handles injection of plugin management into the model.
*
*/
public interface PluginManagementInjector {
/**
* Merges default values from the plugin management section of the given model into itself.
*
* @param model The model into which to merge the values specified by its plugin management section, must not be
* <code>null</code>.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
}

View File

@ -0,0 +1,81 @@
/*
* 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.model;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
/**
* Describes the environmental context used to determine the activation status of profiles.
*
*/
public interface ProfileActivationContext {
/**
* Key of the property containing the project's packaging.
* Available in {@link #getUserProperties()}.
* @since 4.0.0
*/
String PROPERTY_NAME_PACKAGING = "packaging";
/**
* Gets the identifiers of those profiles that should be activated by explicit demand.
*
* @return The identifiers of those profiles to activate, never {@code null}.
*/
List<String> getActiveProfileIds();
/**
* Gets the identifiers of those profiles that should be deactivated by explicit demand.
*
* @return The identifiers of those profiles to deactivate, never {@code null}.
*/
List<String> getInactiveProfileIds();
/**
* Gets the system properties to use for interpolation and profile activation. The system properties are collected
* from the runtime environment like {@link System#getProperties()} and environment variables.
*
* @return The execution properties, never {@code null}.
*/
Map<String, String> getSystemProperties();
/**
* Gets the user properties to use for interpolation and profile activation. The user properties have been
* configured directly by the user on his discretion, e.g. via the {@code -Dkey=value} parameter on the command
* line.
*
* @return The user properties, never {@code null}.
*/
Map<String, String> getUserProperties();
/**
* Gets the base directory of the current project (if any).
*
* @return The base directory of the current project or {@code null} if none.
*/
Path getProjectDirectory();
/**
* Gets current calculated project properties
*
* @return The project properties, never {@code null}.
*/
Map<String, String> getProjectProperties();
}

View File

@ -0,0 +1,54 @@
/*
* 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.model;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Determines whether a profile should be activated.
*
*/
public interface ProfileActivator {
/**
* Determines whether the specified profile is active in the given activator context.
*
* @param profile The profile whose activation status should be determined, must not be {@code null}.
* @param context The environmental context used to determine the activation status of the profile, must not be
* {@code null}.
* @param problems The container used to collect problems (e.g. bad syntax) that were encountered, must not be
* {@code null}.
* @return {@code true} if the profile is active, {@code false} otherwise.
*/
boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems);
/**
* Determines whether specified activation method is present in configuration or not. It should help to have AND
* between activation conditions
* Need for solving https://issues.apache.org/jira/browse/MNG-4565
* @param profile The profile whose activation status should be determined, must not be {@code null}.
* @param context The environmental context used to determine the activation status of the profile, must not be
* {@code null}.
* @param problems The container used to collect problems (e.g. bad syntax) that were encountered, must not be
* {@code null}.
* @return {@code true} if the profile is active, {@code false} otherwise.
*/
boolean presentInConfig(Profile profile, ProfileActivationContext context, ModelProblemCollector problems);
}

View File

@ -0,0 +1,59 @@
/*
* 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.model;
import java.util.List;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Handles profile injection into the model.
*
*/
public interface ProfileInjector {
/**
* Merges values from the specified profile into the given model. Implementations are expected to keep the profile
* and model completely decoupled by injecting deep copies rather than the original objects from the profile.
*
* @param model The model into which to merge the values defined by the profile, must not be <code>null</code>.
* @param profile The (read-only) profile whose values should be injected, may be <code>null</code>.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
default Model injectProfile(
Model model, Profile profile, ModelBuilderRequest request, ModelProblemCollector problems) {
return injectProfiles(model, List.of(profile), request, problems);
}
/**
* Merges values from the specified profile into the given model. Implementations are expected to keep the profile
* and model completely decoupled by injecting deep copies rather than the original objects from the profile.
*
* @param model The model into which to merge the values defined by the profile, must not be <code>null</code>.
* @param profiles The (read-only) list of profiles whose values should be injected, must not be <code>null</code>.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectProfiles(
Model model, List<Profile> profiles, ModelBuilderRequest request, ModelProblemCollector problems);
}

View File

@ -0,0 +1,45 @@
/*
* 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.model;
import java.util.Collection;
import java.util.List;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.ModelProblemCollector;
/**
* Calculates the active profiles among a given collection of profiles.
*
*/
public interface ProfileSelector {
/**
* Determines the profiles which are active in the specified activation context. Active profiles will eventually be
* injected into the model.
*
* @param profiles The profiles whose activation status should be determined, must not be {@code null}.
* @param context The environmental context used to determine the activation status of a profile, must not be
* {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
* @return The profiles that have been activated, never {@code null}.
*/
List<Profile> getActiveProfiles(
Collection<Profile> profiles, ProfileActivationContext context, ModelProblemCollector problems);
}

View File

@ -0,0 +1,67 @@
/*
* 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.model;
import java.nio.file.Path;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
/**
* Interface used to locate the root directory for a given project.
*
* The root locator is usually looked up from the plexus container.
* One notable exception is the computation of the early {@code session.rootDirectory}
* property which happens very early. The implementation used in this case
* will be discovered using the JDK service mechanism.
*
* The default implementation will look for a {@code .mvn} child directory
* or a {@code pom.xml} containing the {@code root="true"} attribute.
*/
public interface RootLocator {
String UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE = "Unable to find the root directory. "
+ "Create a .mvn directory in the root directory or add the root=\"true\""
+ " attribute on the root project's model to identify it.";
@Nonnull
default Path findMandatoryRoot(Path basedir) {
Path rootDirectory = findRoot(basedir);
if (rootDirectory == null) {
throw new IllegalStateException(getNoRootMessage());
}
return rootDirectory;
}
@Nullable
default Path findRoot(Path basedir) {
Path rootDirectory = basedir;
while (rootDirectory != null && !isRootDirectory(rootDirectory)) {
rootDirectory = rootDirectory.getParent();
}
return rootDirectory;
}
@Nonnull
default String getNoRootMessage() {
return UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE;
}
boolean isRootDirectory(Path dir);
}

View File

@ -0,0 +1,30 @@
/*
* 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.model;
public interface UrlNormalizer {
/**
* Normalizes the specified URL.
*
* @param url The URL to normalize, may be {@code null}.
* @return The normalized URL or {@code null} if the input was {@code null}.
*/
String normalize(String url);
}

View File

@ -0,0 +1,31 @@
/*
* 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.model;
import org.apache.maven.api.model.Model;
/**
* WorkspaceModelResolver
*/
public interface WorkspaceModelResolver {
Model resolveRawModel(String groupId, String artifactId, String versionConstraint);
Model resolveEffectiveModel(String groupId, String artifactId, String versionConstraint);
}

View File

@ -90,10 +90,11 @@ public abstract class AbstractSession implements InternalSession {
List<RemoteRepository> repositories, List<RemoteRepository> repositories,
List<org.eclipse.aether.repository.RemoteRepository> resolverRepositories, List<org.eclipse.aether.repository.RemoteRepository> resolverRepositories,
Lookup lookup) { Lookup lookup) {
this.session = session; this.session = nonNull(session, "session");
this.repositorySystem = repositorySystem; this.repositorySystem = repositorySystem;
this.repositories = getRepositories(repositories, resolverRepositories); this.repositories = getRepositories(repositories, resolverRepositories);
this.lookup = lookup; this.lookup = lookup;
this.session.getData().set(InternalSession.class, this);
} }
@Override @Override

View File

@ -24,7 +24,6 @@ import org.apache.maven.api.Artifact;
import org.apache.maven.api.ArtifactCoordinate; import org.apache.maven.api.ArtifactCoordinate;
import org.apache.maven.api.Version; import org.apache.maven.api.Version;
import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.repository.internal.DefaultModelVersionParser;
import static org.apache.maven.internal.impl.Utils.nonNull; import static org.apache.maven.internal.impl.Utils.nonNull;

View File

@ -28,7 +28,6 @@ import org.apache.maven.api.Type;
import org.apache.maven.api.Version; import org.apache.maven.api.Version;
import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.repository.internal.DefaultModelVersionParser;
import org.eclipse.aether.artifact.ArtifactProperties; import org.eclipse.aether.artifact.ArtifactProperties;
import static org.apache.maven.internal.impl.Utils.nonNull; import static org.apache.maven.internal.impl.Utils.nonNull;

View File

@ -0,0 +1,80 @@
/*
* 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 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.DistributionManagement;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Scm;
import org.apache.maven.api.model.Site;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.model.ModelUrlNormalizer;
import org.apache.maven.api.services.model.UrlNormalizer;
/**
* Normalizes URLs to remove the ugly parent references "../" that got potentially inserted by URL adjustment during
* model inheritance.
*
*/
@Named
@Singleton
public class DefaultModelUrlNormalizer implements ModelUrlNormalizer {
private final UrlNormalizer urlNormalizer;
@Inject
public DefaultModelUrlNormalizer(UrlNormalizer urlNormalizer) {
this.urlNormalizer = urlNormalizer;
}
@Override
public Model normalize(Model model, ModelBuilderRequest request) {
if (model == null) {
return null;
}
Model.Builder builder = Model.newBuilder(model);
builder.url(normalize(model.getUrl()));
Scm scm = model.getScm();
if (scm != null) {
builder.scm(Scm.newBuilder(scm)
.url(normalize(scm.getUrl()))
.connection(normalize(scm.getConnection()))
.developerConnection(normalize(scm.getDeveloperConnection()))
.build());
}
DistributionManagement dist = model.getDistributionManagement();
if (dist != null) {
Site site = dist.getSite();
if (site != null) {
builder.distributionManagement(dist.withSite(site.withUrl(normalize(site.getUrl()))));
}
}
return builder.build();
}
private String normalize(String url) {
return urlNormalizer.normalize(url);
}
}

View File

@ -0,0 +1,288 @@
/*
* 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.regex.Pattern;
import org.apache.maven.api.Version;
import org.apache.maven.api.VersionConstraint;
import org.apache.maven.api.VersionRange;
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.services.VersionParserException;
import org.apache.maven.api.services.model.ModelVersionParser;
import org.eclipse.aether.version.InvalidVersionSpecificationException;
import org.eclipse.aether.version.VersionScheme;
import static java.util.Objects.requireNonNull;
@Named
@Singleton
public class DefaultModelVersionParser implements ModelVersionParser {
private static final String SNAPSHOT = "SNAPSHOT";
private static final Pattern SNAPSHOT_TIMESTAMP = Pattern.compile("^(.*-)?([0-9]{8}\\.[0-9]{6}-[0-9]+)$");
private final VersionScheme versionScheme;
@Inject
public DefaultModelVersionParser(VersionScheme versionScheme) {
this.versionScheme = requireNonNull(versionScheme, "versionScheme");
}
@Override
public Version parseVersion(String version) {
requireNonNull(version, "version");
return new DefaultVersion(versionScheme, version);
}
@Override
public VersionRange parseVersionRange(String range) {
requireNonNull(range, "range");
return new DefaultVersionRange(versionScheme, range);
}
@Override
public boolean isSnapshot(String version) {
return checkSnapshot(version);
}
public static boolean checkSnapshot(String version) {
return version.endsWith(SNAPSHOT) || SNAPSHOT_TIMESTAMP.matcher(version).matches();
}
@Override
public VersionConstraint parseVersionConstraint(String constraint) {
requireNonNull(constraint, "constraint");
return new DefaultVersionConstraint(versionScheme, constraint);
}
static class DefaultVersion implements Version {
private final VersionScheme versionScheme;
private final org.eclipse.aether.version.Version delegate;
DefaultVersion(VersionScheme versionScheme, org.eclipse.aether.version.Version delegate) {
this.versionScheme = versionScheme;
this.delegate = delegate;
}
DefaultVersion(VersionScheme versionScheme, String delegateValue) {
this.versionScheme = versionScheme;
try {
this.delegate = versionScheme.parseVersion(delegateValue);
} catch (InvalidVersionSpecificationException e) {
throw new VersionParserException("Unable to parse version: " + delegateValue, e);
}
}
@Override
public int compareTo(Version o) {
if (o instanceof DefaultVersion) {
return delegate.compareTo(((DefaultVersion) o).delegate);
} else {
return compareTo(new DefaultVersion(versionScheme, o.asString()));
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DefaultVersion that = (DefaultVersion) o;
return delegate.equals(that.delegate);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public String asString() {
return delegate.toString();
}
@Override
public String toString() {
return asString();
}
}
static class DefaultVersionRange implements VersionRange {
private final VersionScheme versionScheme;
private final org.eclipse.aether.version.VersionRange delegate;
DefaultVersionRange(VersionScheme versionScheme, org.eclipse.aether.version.VersionRange delegate) {
this.versionScheme = versionScheme;
this.delegate = delegate;
}
DefaultVersionRange(VersionScheme versionScheme, String delegateValue) {
this.versionScheme = versionScheme;
try {
this.delegate = versionScheme.parseVersionRange(delegateValue);
} catch (InvalidVersionSpecificationException e) {
throw new VersionParserException("Unable to parse version range: " + delegateValue, e);
}
}
@Override
public boolean contains(Version version) {
if (version instanceof DefaultVersion) {
return delegate.containsVersion(((DefaultVersion) version).delegate);
} else {
return contains(new DefaultVersion(versionScheme, version.asString()));
}
}
@Override
public Boundary getUpperBoundary() {
org.eclipse.aether.version.VersionRange.Bound bound = delegate.getUpperBound();
if (bound == null) {
return null;
}
return new Boundary() {
@Override
public Version getVersion() {
return new DefaultVersion(versionScheme, bound.getVersion());
}
@Override
public boolean isInclusive() {
return bound.isInclusive();
}
};
}
@Override
public Boundary getLowerBoundary() {
org.eclipse.aether.version.VersionRange.Bound bound = delegate.getLowerBound();
if (bound == null) {
return null;
}
return new Boundary() {
@Override
public Version getVersion() {
return new DefaultVersion(versionScheme, bound.getVersion());
}
@Override
public boolean isInclusive() {
return bound.isInclusive();
}
};
}
@Override
public String asString() {
return delegate.toString();
}
@Override
public String toString() {
return asString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DefaultVersionRange that = (DefaultVersionRange) o;
return delegate.equals(that.delegate);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
}
static class DefaultVersionConstraint implements VersionConstraint {
private final VersionScheme versionScheme;
private final org.eclipse.aether.version.VersionConstraint delegate;
DefaultVersionConstraint(VersionScheme versionScheme, String delegateValue) {
this.versionScheme = versionScheme;
try {
this.delegate = versionScheme.parseVersionConstraint(delegateValue);
} catch (InvalidVersionSpecificationException e) {
throw new VersionParserException("Unable to parse version constraint: " + delegateValue, e);
}
}
@Override
public boolean contains(Version version) {
if (version instanceof DefaultVersion) {
return delegate.containsVersion(((DefaultVersion) version).delegate);
} else {
return contains(new DefaultVersion(versionScheme, version.asString()));
}
}
@Override
public String asString() {
return delegate.toString();
}
@Override
public VersionRange getVersionRange() {
if (delegate.getRange() == null) {
return null;
}
return new DefaultVersionRange(versionScheme, delegate.getRange());
}
@Override
public Version getRecommendedVersion() {
if (delegate.getVersion() == null) {
return null;
}
return new DefaultVersion(versionScheme, delegate.getVersion());
}
@Override
public String toString() {
return asString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DefaultVersionConstraint that = (DefaultVersionConstraint) o;
return delegate.equals(that.delegate);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
}
}

View File

@ -59,7 +59,8 @@ public class DefaultModelXmlFactory implements ModelXmlFactory {
try { try {
InputSource source = null; InputSource source = null;
if (request.getModelId() != null || request.getLocation() != null) { if (request.getModelId() != null || request.getLocation() != null) {
source = new InputSource(request.getModelId(), request.getLocation()); source = new InputSource(
request.getModelId(), path != null ? path.toUri().toString() : null);
} }
MavenStaxReader xml = new MavenStaxReader(); MavenStaxReader xml = new MavenStaxReader();
xml.setAddDefaultEntities(request.isAddDefaultEntities()); xml.setAddDefaultEntities(request.isAddDefaultEntities());

View File

@ -0,0 +1,107 @@
/*
* 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.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.model.Build;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Plugin;
import org.apache.maven.api.model.PluginManagement;
import org.apache.maven.api.model.ReportPlugin;
import org.apache.maven.api.model.Reporting;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.PluginConfigurationExpander;
import org.apache.maven.api.xml.XmlNode;
/**
* Handles expansion of general build plugin configuration into individual executions.
*
*/
@Named
@Singleton
public class DefaultPluginConfigurationExpander implements PluginConfigurationExpander {
@Override
public Model expandPluginConfiguration(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
Build build = model.getBuild();
if (build != null) {
build = build.withPlugins(expandPlugin(build.getPlugins()));
PluginManagement pluginManagement = build.getPluginManagement();
if (pluginManagement != null) {
build = build.withPluginManagement(
pluginManagement.withPlugins(expandPlugin(pluginManagement.getPlugins())));
}
model = model.withBuild(build);
}
Reporting reporting = model.getReporting();
if (reporting != null) {
expandReport(reporting.getPlugins());
}
return model.withBuild(build);
}
private List<Plugin> expandPlugin(List<Plugin> oldPlugins) {
return map(oldPlugins, plugin -> {
XmlNode pluginConfiguration = plugin.getConfiguration();
if (pluginConfiguration != null) {
return plugin.withExecutions(map(
plugin.getExecutions(),
execution -> execution.withConfiguration(
XmlNode.merge(execution.getConfiguration(), pluginConfiguration))));
} else {
return plugin;
}
});
}
private List<ReportPlugin> expandReport(List<ReportPlugin> oldPlugins) {
return map(oldPlugins, plugin -> {
XmlNode pluginConfiguration = plugin.getConfiguration();
if (pluginConfiguration != null) {
return plugin.withReportSets(map(
plugin.getReportSets(),
report -> report.withConfiguration(
XmlNode.merge(report.getConfiguration(), pluginConfiguration))));
} else {
return plugin;
}
});
}
static <T> List<T> map(List<T> list, Function<T, T> mapper) {
List<T> newList = list;
for (int i = 0; i < newList.size(); i++) {
T oldT = newList.get(i);
T newT = mapper.apply(oldT);
if (newT != oldT) {
if (newList == list) {
newList = new ArrayList<>(list);
}
newList.set(i, newT);
}
}
return newList;
}
}

View File

@ -55,4 +55,9 @@ public class DefaultRemoteRepository implements RemoteRepository {
public String getProtocol() { public String getProtocol() {
return repository.getProtocol(); return repository.getProtocol();
} }
@Override
public String toString() {
return repository.toString();
}
} }

View File

@ -137,11 +137,7 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
Settings settings; Settings settings;
try { try {
try { try (InputStream is = settingsSource.openStream()) {
InputStream is = settingsSource.openStream();
if (is == null) {
return Settings.newInstance();
}
settings = request.getSession() settings = request.getSession()
.getService(SettingsXmlFactory.class) .getService(SettingsXmlFactory.class)
.read(XmlReaderRequest.builder() .read(XmlReaderRequest.builder()
@ -150,25 +146,23 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
.strict(true) .strict(true)
.build()); .build());
} catch (XmlReaderException e) { } catch (XmlReaderException e) {
InputStream is = settingsSource.openStream(); try (InputStream is = settingsSource.openStream()) {
if (is == null) { settings = request.getSession()
return Settings.newInstance(); .getService(SettingsXmlFactory.class)
.read(XmlReaderRequest.builder()
.inputStream(is)
.location(settingsSource.getLocation())
.strict(false)
.build());
Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
problems.add(new DefaultBuilderProblem(
settingsSource.getLocation(),
loc != null ? loc.getLineNumber() : -1,
loc != null ? loc.getColumnNumber() : -1,
e,
e.getMessage(),
BuilderProblem.Severity.WARNING));
} }
settings = request.getSession()
.getService(SettingsXmlFactory.class)
.read(XmlReaderRequest.builder()
.inputStream(is)
.location(settingsSource.getLocation())
.strict(false)
.build());
Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
problems.add(new DefaultBuilderProblem(
settingsSource.getLocation(),
loc != null ? loc.getLineNumber() : -1,
loc != null ? loc.getColumnNumber() : -1,
e,
e.getMessage(),
BuilderProblem.Severity.WARNING));
} }
} catch (XmlReaderException e) { } catch (XmlReaderException e) {
Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null; Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;

View File

@ -0,0 +1,80 @@
/*
* 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.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
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.Model;
import org.apache.maven.api.services.SuperPomProvider;
import org.apache.maven.api.services.model.ModelProcessor;
import org.apache.maven.api.services.xml.XmlReaderRequest;
@Named
@Singleton
public class DefaultSuperPomProvider implements SuperPomProvider {
private final ModelProcessor modelProcessor;
/**
* The cached super POM, lazily created.
*/
private static final Map<String, Model> SUPER_MODELS = new ConcurrentHashMap<>();
@Inject
public DefaultSuperPomProvider(ModelProcessor modelProcessor) {
this.modelProcessor = modelProcessor;
}
@Override
public Model getSuperPom(String version) {
return SUPER_MODELS.computeIfAbsent(Objects.requireNonNull(version), v -> readModel(version, v));
}
private Model readModel(String version, String v) {
String resource = "/org/apache/maven/model/pom-" + v + ".xml";
URL url = getClass().getResource(resource);
if (url == null) {
throw new IllegalStateException("The super POM " + resource + " was not found"
+ ", please verify the integrity of your Maven installation");
}
try (InputStream is = url.openStream()) {
String modelId = "org.apache.maven:maven-model-builder:" + version + "-"
+ this.getClass().getPackage().getImplementationVersion() + ":super-pom";
return modelProcessor.read(XmlReaderRequest.builder()
.modelId(modelId)
.location(url.toExternalForm())
.inputStream(is)
.strict(false)
.build());
} catch (IOException e) {
throw new IllegalStateException(
"The super POM " + resource + " is damaged"
+ ", please verify the integrity of your Maven installation",
e);
}
}
}

View File

@ -0,0 +1,61 @@
/*
* 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 org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.services.model.UrlNormalizer;
/**
* Normalizes a URL.
*
*/
@Named
@Singleton
public class DefaultUrlNormalizer implements UrlNormalizer {
@Override
public String normalize(String url) {
String result = url;
if (result != null) {
while (true) {
int idx = result.indexOf("/../");
if (idx < 0) {
break;
} else if (idx == 0) {
result = result.substring(3);
continue;
}
int parent = idx - 1;
while (parent >= 0 && result.charAt(parent) == '/') {
parent--;
}
parent = result.lastIndexOf('/', parent);
if (parent < 0) {
result = result.substring(idx + 4);
} else {
result = result.substring(0, parent) + result.substring(idx + 3);
}
}
}
return result;
}
}

View File

@ -25,7 +25,7 @@ import org.apache.maven.api.di.Inject;
import org.apache.maven.api.di.Named; import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton; import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.services.VersionParser; import org.apache.maven.api.services.VersionParser;
import org.apache.maven.model.version.ModelVersionParser; import org.apache.maven.api.services.model.ModelVersionParser;
import static org.apache.maven.internal.impl.Utils.nonNull; import static org.apache.maven.internal.impl.Utils.nonNull;

View File

@ -66,8 +66,9 @@ public class DefaultVersionRangeResolver implements VersionRangeResolver {
session.toRepositories(session.getRemoteRepositories()), session.toRepositories(session.getRemoteRepositories()),
null)); null));
Map<String, ArtifactRepository> repos = Map<String, ArtifactRepository> repos = res.getVersions().stream()
res.getVersions().stream().collect(Collectors.toMap(v -> v.toString(), res::getRepository)); .filter(v -> res.getRepository(v) != null)
.collect(Collectors.toMap(v -> v.toString(), res::getRepository));
return new VersionRangeResolverResult() { return new VersionRangeResolverResult() {
@Override @Override

View File

@ -18,21 +18,29 @@
*/ */
package org.apache.maven.internal.impl; package org.apache.maven.internal.impl;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.maven.SessionScoped; import org.apache.maven.api.ExtensibleEnum;
import org.apache.maven.api.*; import org.apache.maven.api.Language;
import org.apache.maven.api.services.*; import org.apache.maven.api.PathScope;
import org.apache.maven.api.spi.*; import org.apache.maven.api.ProjectScope;
import org.apache.maven.api.di.Inject;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.SessionScoped;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.services.ExtensibleEnumRegistry;
import org.apache.maven.api.services.LanguageRegistry;
import org.apache.maven.api.services.PathScopeRegistry;
import org.apache.maven.api.services.ProjectScopeRegistry;
import org.apache.maven.api.spi.ExtensibleEnumProvider;
import org.apache.maven.api.spi.LanguageProvider;
import org.apache.maven.api.spi.PathScopeProvider;
import org.apache.maven.api.spi.ProjectScopeProvider;
import static org.apache.maven.internal.impl.Utils.nonNull; import static org.apache.maven.internal.impl.Utils.nonNull;
@ -76,7 +84,8 @@ public class ExtensibleEnumRegistries {
} }
} }
static class DefaultExtensibleEnumRegistry<T extends ExtensibleEnum, P extends ExtensibleEnumProvider<T>> public abstract static class DefaultExtensibleEnumRegistry<
T extends ExtensibleEnum, P extends ExtensibleEnumProvider<T>>
implements ExtensibleEnumRegistry<T> { implements ExtensibleEnumRegistry<T> {
protected final Map<String, T> values; protected final Map<String, T> values;
@ -84,12 +93,12 @@ public class ExtensibleEnumRegistries {
DefaultExtensibleEnumRegistry(List<P> providers, T... builtinValues) { DefaultExtensibleEnumRegistry(List<P> providers, T... builtinValues) {
values = Stream.<T>concat( values = Stream.<T>concat(
Stream.of(builtinValues), providers.stream().flatMap(p -> p.provides().stream())) Stream.of(builtinValues), providers.stream().flatMap(p -> p.provides().stream()))
.collect(Collectors.toUnmodifiableMap(ExtensibleEnum::id, Function.identity())); .collect(Collectors.toUnmodifiableMap(t -> t.id().toLowerCase(Locale.ROOT), t -> t));
} }
@Override @Override
public Optional<T> lookup(String id) { public Optional<T> lookup(String id) {
return Optional.ofNullable(values.get(nonNull(id, "id"))); return Optional.ofNullable(values.get(nonNull(id, "id").toLowerCase(Locale.ROOT)));
} }
} }
} }

View File

@ -41,6 +41,10 @@ public interface InternalSession extends Session {
return cast(InternalSession.class, session, "session should be an " + InternalSession.class); return cast(InternalSession.class, session, "session should be an " + InternalSession.class);
} }
static InternalSession from(org.eclipse.aether.RepositorySystemSession session) {
return cast(InternalSession.class, session.getData().get(InternalSession.class), "session");
}
RemoteRepository getRemoteRepository(org.eclipse.aether.repository.RemoteRepository repository); RemoteRepository getRemoteRepository(org.eclipse.aether.repository.RemoteRepository repository);
Node getNode(org.eclipse.aether.graph.DependencyNode node); Node getNode(org.eclipse.aether.graph.DependencyNode node);

View File

@ -40,6 +40,9 @@ class Utils {
static <T> T cast(Class<T> clazz, Object o, String name) { static <T> T cast(Class<T> clazz, Object o, String name) {
if (!clazz.isInstance(o)) { if (!clazz.isInstance(o)) {
if (o == null) {
throw new IllegalArgumentException(name + " is null");
}
throw new IllegalArgumentException(name + " is not an instance of " + clazz.getName()); throw new IllegalArgumentException(name + " is not an instance of " + clazz.getName());
} }
return clazz.cast(o); return clazz.cast(o);

View File

@ -0,0 +1,199 @@
/*
* 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.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.model.Dependency;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Parent;
import org.apache.maven.api.services.ModelTransformer;
import org.apache.maven.api.services.ModelTransformerContext;
/**
* ModelSourceTransformer for the build pom
*
* @since 4.0.0
*/
@Named
@Singleton
public class BuildModelTransformer implements ModelTransformer {
public static final String NAMESPACE_PREFIX = "http://maven.apache.org/POM/";
@Override
public Model transform(ModelTransformerContext context, Model model, Path path) {
Model.Builder builder = Model.newBuilder(model);
handleModelVersion(context, model, path, builder);
handleParent(context, model, path, builder);
handleReactorDependencies(context, model, path, builder);
handleCiFriendlyVersion(context, model, path, builder);
return builder.build();
}
//
// Infer modelVersion from namespace URI
//
void handleModelVersion(ModelTransformerContext context, Model model, Path pomFile, Model.Builder builder) {
String namespace = model.getNamespaceUri();
if (model.getModelVersion() == null && namespace != null && namespace.startsWith(NAMESPACE_PREFIX)) {
builder.modelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
}
}
//
// Infer parent information
//
void handleParent(ModelTransformerContext context, Model model, Path pomFile, Model.Builder builder) {
Parent parent = model.getParent();
if (parent != null) {
String version = parent.getVersion();
String path = Optional.ofNullable(parent.getRelativePath()).orElse("..");
if (version == null && !path.isEmpty()) {
Optional<RelativeProject> resolvedParent = resolveRelativePath(
pomFile, context, Paths.get(path), parent.getGroupId(), parent.getArtifactId());
if (resolvedParent.isPresent()) {
version = resolvedParent.get().getVersion();
}
}
// CI Friendly version for parent
String modVersion = replaceCiFriendlyVersion(context, version);
builder.parent(parent.withVersion(modVersion));
}
}
//
// CI friendly versions
//
void handleCiFriendlyVersion(ModelTransformerContext context, Model model, Path pomFile, Model.Builder builder) {
String version = model.getVersion();
String modVersion = replaceCiFriendlyVersion(context, version);
builder.version(modVersion);
}
//
// Infer inner reactor dependencies version
//
void handleReactorDependencies(ModelTransformerContext context, Model model, Path pomFile, Model.Builder builder) {
List<Dependency> newDeps = new ArrayList<>();
boolean modified = false;
for (Dependency dep : model.getDependencies()) {
if (dep.getVersion() == null) {
Model depModel = context.getRawModel(model.getPomFile(), dep.getGroupId(), dep.getArtifactId());
if (depModel != null) {
String v = depModel.getVersion();
if (v == null && depModel.getParent() != null) {
v = depModel.getParent().getVersion();
}
dep = dep.withVersion(v);
modified = true;
}
}
newDeps.add(dep);
}
if (modified) {
builder.dependencies(newDeps);
}
}
protected String replaceCiFriendlyVersion(ModelTransformerContext context, String version) {
if (version != null) {
for (String key : Arrays.asList("changelist", "revision", "sha1")) {
String val = context.getUserProperty(key);
if (val != null) {
version = version.replace("${" + key + "}", val);
}
}
}
return version;
}
protected Optional<RelativeProject> resolveRelativePath(
Path pomFile, ModelTransformerContext context, Path relativePath, String groupId, String artifactId) {
Path pomPath = pomFile.resolveSibling(relativePath).normalize();
if (Files.isDirectory(pomPath)) {
pomPath = context.locate(pomPath);
}
if (pomPath == null || !Files.isRegularFile(pomPath)) {
return Optional.empty();
}
Optional<RelativeProject> mappedProject = Optional.ofNullable(context.getRawModel(pomFile, pomPath.normalize()))
.map(BuildModelTransformer::toRelativeProject);
if (mappedProject.isPresent()) {
RelativeProject project = mappedProject.get();
if (Objects.equals(groupId, project.getGroupId()) && Objects.equals(artifactId, project.getArtifactId())) {
return mappedProject;
}
}
return Optional.empty();
}
private static RelativeProject toRelativeProject(final Model m) {
String groupId = m.getGroupId();
if (groupId == null && m.getParent() != null) {
groupId = m.getParent().getGroupId();
}
String version = m.getVersion();
if (version == null && m.getParent() != null) {
version = m.getParent().getVersion();
}
return new RelativeProject(groupId, m.getArtifactId(), version);
}
protected static class RelativeProject {
private final String groupId;
private final String artifactId;
private final String version;
protected RelativeProject(String groupId, String artifactId, String version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
}
public String getGroupId() {
return groupId;
}
public String getArtifactId() {
return artifactId;
}
public String getVersion() {
return version;
}
}
}

View File

@ -0,0 +1,147 @@
/*
* 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.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
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.Model;
import org.apache.maven.api.services.BuilderProblem.Severity;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblem.Version;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.*;
/**
* Handles the import of dependency management from other models into the target model.
*
*/
@Named
@Singleton
public class DefaultDependencyManagementImporter implements DependencyManagementImporter {
@Override
public Model importManagement(
Model target,
List<? extends DependencyManagement> sources,
ModelBuilderRequest request,
ModelProblemCollector problems) {
if (sources != null && !sources.isEmpty()) {
Map<String, Dependency> dependencies = new LinkedHashMap<>();
DependencyManagement depMgmt = target.getDependencyManagement();
if (depMgmt != null) {
for (Dependency dependency : depMgmt.getDependencies()) {
dependencies.put(dependency.getManagementKey(), dependency);
}
} else {
depMgmt = DependencyManagement.newInstance();
}
Set<String> directDependencies = new HashSet<>(dependencies.keySet());
for (DependencyManagement source : sources) {
for (Dependency dependency : source.getDependencies()) {
String key = dependency.getManagementKey();
Dependency present = dependencies.putIfAbsent(key, dependency);
if (present != null && !equals(dependency, present) && !directDependencies.contains(key)) {
// TODO: https://issues.apache.org/jira/browse/MNG-8004
problems.add(
Severity.WARNING,
Version.V40,
"Ignored POM import for: " + toString(dependency) + " as already imported "
+ toString(present) + ". Add a the conflicting managed dependency directly "
+ "to the dependencyManagement section of the POM.");
}
}
}
return target.withDependencyManagement(depMgmt.withDependencies(dependencies.values()));
}
return target;
}
private String toString(Dependency dependency) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder
.append(dependency.getGroupId())
.append(":")
.append(dependency.getArtifactId())
.append(":")
.append(dependency.getType());
if (dependency.getClassifier() != null && !dependency.getClassifier().isEmpty()) {
stringBuilder.append(":").append(dependency.getClassifier());
}
stringBuilder
.append(":")
.append(dependency.getVersion())
.append("@")
.append(dependency.getScope() == null ? "compile" : dependency.getScope());
if (dependency.isOptional()) {
stringBuilder.append("[optional]");
}
if (!dependency.getExclusions().isEmpty()) {
stringBuilder.append("[").append(dependency.getExclusions().size()).append(" exclusions]");
}
return stringBuilder.toString();
}
private boolean equals(Dependency d1, Dependency d2) {
return Objects.equals(d1.getGroupId(), d2.getGroupId())
&& Objects.equals(d1.getArtifactId(), d2.getArtifactId())
&& Objects.equals(d1.getVersion(), d2.getVersion())
&& Objects.equals(d1.getType(), d2.getType())
&& Objects.equals(d1.getClassifier(), d2.getClassifier())
&& Objects.equals(d1.getScope(), d2.getScope())
&& Objects.equals(d1.getSystemPath(), d2.getSystemPath())
&& Objects.equals(d1.getOptional(), d2.getOptional())
&& equals(d1.getExclusions(), d2.getExclusions());
}
private boolean equals(Collection<Exclusion> ce1, Collection<Exclusion> ce2) {
if (ce1.size() == ce2.size()) {
Iterator<Exclusion> i1 = ce1.iterator();
Iterator<Exclusion> i2 = ce2.iterator();
while (i1.hasNext() && i2.hasNext()) {
if (!equals(i1.next(), i2.next())) {
return false;
}
}
return !i1.hasNext() && !i2.hasNext();
}
return false;
}
private boolean equals(Exclusion e1, Exclusion e2) {
return Objects.equals(e1.getGroupId(), e2.getGroupId())
&& Objects.equals(e1.getArtifactId(), e2.getArtifactId());
}
}

View File

@ -0,0 +1,119 @@
/*
* 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.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
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.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.*;
/**
* Handles injection of dependency management into the model.
*
*/
@SuppressWarnings({"checkstyle:methodname"})
@Named
@Singleton
public class DefaultDependencyManagementInjector implements DependencyManagementInjector {
private ManagementModelMerger merger = new ManagementModelMerger();
@Override
public Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
return merger.mergeManagedDependencies(model);
}
/**
* ManagementModelMerger
*/
protected static class ManagementModelMerger extends MavenModelMerger {
public Model mergeManagedDependencies(Model model) {
DependencyManagement dependencyManagement = model.getDependencyManagement();
if (dependencyManagement != null) {
Map<Object, Dependency> dependencies = new HashMap<>();
Map<Object, Object> context = Collections.emptyMap();
for (Dependency dependency : model.getDependencies()) {
Object key = getDependencyKey().apply(dependency);
dependencies.put(key, dependency);
}
boolean modified = false;
for (Dependency managedDependency : dependencyManagement.getDependencies()) {
Object key = getDependencyKey().apply(managedDependency);
Dependency dependency = dependencies.get(key);
if (dependency != null) {
Dependency merged = mergeDependency(dependency, managedDependency, false, context);
if (merged != dependency) {
dependencies.put(key, merged);
modified = true;
}
}
}
if (modified) {
List<Dependency> newDeps = new ArrayList<>(dependencies.size());
for (Dependency dep : model.getDependencies()) {
Object key = getDependencyKey().apply(dep);
Dependency dependency = dependencies.get(key);
newDeps.add(dependency);
}
return Model.newBuilder(model).dependencies(newDeps).build();
}
}
return model;
}
@Override
protected void mergeDependency_Optional(
Dependency.Builder builder,
Dependency target,
Dependency source,
boolean sourceDominant,
Map<Object, Object> context) {
// optional flag is not managed
}
@Override
protected void mergeDependency_Exclusions(
Dependency.Builder builder,
Dependency target,
Dependency source,
boolean sourceDominant,
Map<Object, Object> context) {
List<Exclusion> tgt = target.getExclusions();
if (tgt.isEmpty()) {
List<Exclusion> src = source.getExclusions();
builder.exclusions(src);
}
}
}
}

View File

@ -0,0 +1,330 @@
/*
* 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.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.model.InputLocation;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.ModelBase;
import org.apache.maven.api.model.Plugin;
import org.apache.maven.api.model.PluginContainer;
import org.apache.maven.api.model.ReportPlugin;
import org.apache.maven.api.model.Reporting;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.*;
/**
* Handles inheritance of model values.
*
*/
@SuppressWarnings({"checkstyle:methodname"})
@Named
@Singleton
public class DefaultInheritanceAssembler implements InheritanceAssembler {
private static final String CHILD_DIRECTORY = "child-directory";
private static final String CHILD_DIRECTORY_PROPERTY = "project.directory";
private final InheritanceModelMerger merger = new InheritanceModelMerger();
@Override
public Model assembleModelInheritance(
Model child, Model parent, ModelBuilderRequest request, ModelProblemCollector problems) {
Map<Object, Object> hints = new HashMap<>();
String childPath = child.getProperties().getOrDefault(CHILD_DIRECTORY_PROPERTY, child.getArtifactId());
hints.put(CHILD_DIRECTORY, childPath);
hints.put(MavenModelMerger.CHILD_PATH_ADJUSTMENT, getChildPathAdjustment(child, parent, childPath));
return merger.merge(child, parent, false, hints);
}
/**
* Calculates the relative path from the base directory of the parent to the parent directory of the base directory
* of the child. The general idea is to adjust inherited URLs to match the project layout (in SCM).
*
* <p>This calculation is only a heuristic based on our conventions.
* In detail, the algo relies on the following assumptions: <ul>
* <li>The parent uses aggregation and refers to the child via the modules section</li>
* <li>The module path to the child is considered to
* point at the POM rather than its base directory if the path ends with ".xml" (ignoring case)</li>
* <li>The name of the child's base directory matches the artifact id of the child.</li>
* </ul>
* Note that for the sake of independence from the user
* environment, the filesystem is intentionally not used for the calculation.</p>
*
* @param child The child model, must not be <code>null</code>.
* @param parent The parent model, may be <code>null</code>.
* @param childDirectory The directory defined in child model, may be <code>null</code>.
* @return The path adjustment, can be empty but never <code>null</code>.
*/
private String getChildPathAdjustment(Model child, Model parent, String childDirectory) {
String adjustment = "";
if (parent != null) {
String childName = child.getArtifactId();
/*
* This logic (using filesystem, against wanted independence from the user environment) exists only for the
* sake of backward-compat with 2.x (MNG-5000). In general, it is wrong to
* base URL inheritance on the module directory names as this information is unavailable for POMs in the
* repository. In other words, modules where artifactId != moduleDirName will see different effective URLs
* depending on how the model was constructed (from filesystem or from repository).
*/
if (child.getProjectDirectory() != null) {
childName = child.getProjectDirectory().getFileName().toString();
}
for (String module : parent.getModules()) {
module = module.replace('\\', '/');
if (module.regionMatches(true, module.length() - 4, ".xml", 0, 4)) {
module = module.substring(0, module.lastIndexOf('/') + 1);
}
String moduleName = module;
if (moduleName.endsWith("/")) {
moduleName = moduleName.substring(0, moduleName.length() - 1);
}
int lastSlash = moduleName.lastIndexOf('/');
moduleName = moduleName.substring(lastSlash + 1);
if ((moduleName.equals(childName) || (moduleName.equals(childDirectory))) && lastSlash >= 0) {
adjustment = module.substring(0, lastSlash);
break;
}
}
}
return adjustment;
}
/**
* InheritanceModelMerger
*/
protected static class InheritanceModelMerger extends MavenModelMerger {
@Override
protected String extrapolateChildUrl(String parentUrl, boolean appendPath, Map<Object, Object> context) {
Object childDirectory = context.get(CHILD_DIRECTORY);
Object childPathAdjustment = context.get(CHILD_PATH_ADJUSTMENT);
boolean isBlankParentUrl = true;
if (parentUrl != null) {
for (int i = 0; i < parentUrl.length(); i++) {
if (!Character.isWhitespace(parentUrl.charAt(i))) {
isBlankParentUrl = false;
}
}
}
if (isBlankParentUrl || childDirectory == null || childPathAdjustment == null || !appendPath) {
return parentUrl;
}
// append childPathAdjustment and childDirectory to parent url
return appendPath(parentUrl, childDirectory.toString(), childPathAdjustment.toString());
}
private String appendPath(String parentUrl, String childPath, String pathAdjustment) {
StringBuilder url = new StringBuilder(parentUrl.length()
+ pathAdjustment.length()
+ childPath.length()
+ (pathAdjustment.isEmpty() ? 1 : 2));
url.append(parentUrl);
concatPath(url, pathAdjustment);
concatPath(url, childPath);
return url.toString();
}
private void concatPath(StringBuilder url, String path) {
if (!path.isEmpty()) {
boolean initialUrlEndsWithSlash = url.charAt(url.length() - 1) == '/';
boolean pathStartsWithSlash = path.charAt(0) == '/';
if (pathStartsWithSlash) {
if (initialUrlEndsWithSlash) {
// 1 extra '/' to remove
url.setLength(url.length() - 1);
}
} else if (!initialUrlEndsWithSlash) {
// add missing '/' between url and path
url.append('/');
}
url.append(path);
// ensure resulting url ends with slash if initial url was
if (initialUrlEndsWithSlash && !path.endsWith("/")) {
url.append('/');
}
}
}
@Override
protected void mergeModelBase_Properties(
ModelBase.Builder builder,
ModelBase target,
ModelBase source,
boolean sourceDominant,
Map<Object, Object> context) {
Map<String, String> merged = new HashMap<>();
if (sourceDominant) {
merged.putAll(target.getProperties());
putAll(merged, source.getProperties(), CHILD_DIRECTORY_PROPERTY);
} else {
putAll(merged, source.getProperties(), CHILD_DIRECTORY_PROPERTY);
merged.putAll(target.getProperties());
}
builder.properties(merged);
builder.location(
"properties",
InputLocation.merge(
target.getLocation("properties"), source.getLocation("properties"), sourceDominant));
}
private void putAll(Map<String, String> s, Map<String, String> t, Object excludeKey) {
for (Map.Entry<String, String> e : t.entrySet()) {
if (!e.getKey().equals(excludeKey)) {
s.put(e.getKey(), e.getValue());
}
}
}
@Override
protected void mergePluginContainer_Plugins(
PluginContainer.Builder builder,
PluginContainer target,
PluginContainer source,
boolean sourceDominant,
Map<Object, Object> context) {
List<Plugin> src = source.getPlugins();
if (!src.isEmpty()) {
List<Plugin> tgt = target.getPlugins();
Map<Object, Plugin> master = new LinkedHashMap<>(src.size() * 2);
for (Plugin element : src) {
if (element.isInherited() || !element.getExecutions().isEmpty()) {
// NOTE: Enforce recursive merge to trigger merging/inheritance logic for executions
Plugin plugin = Plugin.newInstance(false);
plugin = mergePlugin(plugin, element, sourceDominant, context);
Object key = getPluginKey().apply(plugin);
master.put(key, plugin);
}
}
Map<Object, List<Plugin>> predecessors = new LinkedHashMap<>();
List<Plugin> pending = new ArrayList<>();
for (Plugin element : tgt) {
Object key = getPluginKey().apply(element);
Plugin existing = master.get(key);
if (existing != null) {
element = mergePlugin(element, existing, sourceDominant, context);
master.put(key, element);
if (!pending.isEmpty()) {
predecessors.put(key, pending);
pending = new ArrayList<>();
}
} else {
pending.add(element);
}
}
List<Plugin> result = new ArrayList<>(src.size() + tgt.size());
for (Map.Entry<Object, Plugin> entry : master.entrySet()) {
List<Plugin> pre = predecessors.get(entry.getKey());
if (pre != null) {
result.addAll(pre);
}
result.add(entry.getValue());
}
result.addAll(pending);
builder.plugins(result);
}
}
@Override
protected Plugin mergePlugin(
Plugin target, Plugin source, boolean sourceDominant, Map<Object, Object> context) {
Plugin.Builder builder = Plugin.newBuilder(target);
if (source.isInherited()) {
mergeConfigurationContainer(builder, target, source, sourceDominant, context);
}
mergePlugin_GroupId(builder, target, source, sourceDominant, context);
mergePlugin_ArtifactId(builder, target, source, sourceDominant, context);
mergePlugin_Version(builder, target, source, sourceDominant, context);
mergePlugin_Extensions(builder, target, source, sourceDominant, context);
mergePlugin_Executions(builder, target, source, sourceDominant, context);
mergePlugin_Dependencies(builder, target, source, sourceDominant, context);
return builder.build();
}
@Override
protected void mergeReporting_Plugins(
Reporting.Builder builder,
Reporting target,
Reporting source,
boolean sourceDominant,
Map<Object, Object> context) {
List<ReportPlugin> src = source.getPlugins();
if (!src.isEmpty()) {
List<ReportPlugin> tgt = target.getPlugins();
Map<Object, ReportPlugin> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
for (ReportPlugin element : src) {
if (element.isInherited()) {
// NOTE: Enforce recursive merge to trigger merging/inheritance logic for executions as well
ReportPlugin plugin = ReportPlugin.newInstance(false);
plugin = mergeReportPlugin(plugin, element, sourceDominant, context);
merged.put(getReportPluginKey().apply(element), plugin);
}
}
for (ReportPlugin element : tgt) {
Object key = getReportPluginKey().apply(element);
ReportPlugin existing = merged.get(key);
if (existing != null) {
element = mergeReportPlugin(element, existing, sourceDominant, context);
}
merged.put(key, element);
}
builder.plugins(merged.values());
}
}
}
}

View File

@ -0,0 +1,212 @@
/*
* 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.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.maven.api.Packaging;
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.Build;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Plugin;
import org.apache.maven.api.model.PluginContainer;
import org.apache.maven.api.model.PluginExecution;
import org.apache.maven.api.model.PluginManagement;
import org.apache.maven.api.services.BuilderProblem.Severity;
import org.apache.maven.api.services.LifecycleRegistry;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblem.Version;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.PackagingRegistry;
import org.apache.maven.api.services.model.*;
/**
* Handles injection of plugin executions induced by the lifecycle bindings for a packaging.
*
*/
@Named
@Singleton
public class DefaultLifecycleBindingsInjector implements LifecycleBindingsInjector {
private final LifecycleBindingsMerger merger = new LifecycleBindingsMerger();
private final LifecycleRegistry lifecycleRegistry;
private final PackagingRegistry packagingRegistry;
@Inject
public DefaultLifecycleBindingsInjector(LifecycleRegistry lifecycleRegistry, PackagingRegistry packagingRegistry) {
this.lifecycleRegistry = lifecycleRegistry;
this.packagingRegistry = packagingRegistry;
}
public Model injectLifecycleBindings(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
String packagingId = model.getPackaging();
Packaging packaging = packagingRegistry.lookup(packagingId).orElse(null);
if (packaging == null) {
problems.add(
Severity.ERROR, Version.BASE, "Unknown packaging: " + packagingId, model.getLocation("packaging"));
return model;
} else {
Map<String, PluginContainer> plugins = new HashMap<>(packaging.plugins());
lifecycleRegistry.stream()
.filter(lf -> !plugins.containsKey(lf.id()))
.forEach(lf -> plugins.put(
lf.id(),
PluginContainer.newBuilder()
.plugins(lf.phases().stream()
.flatMap(phase -> phase.plugins().stream())
.toList())
.build()));
Map<Plugin, Plugin> allPlugins = new LinkedHashMap<>();
plugins.values().stream().flatMap(pc -> pc.getPlugins().stream()).forEach(p -> addPlugin(allPlugins, p));
Model lifecycleModel = Model.newBuilder()
.build(Build.newBuilder().plugins(allPlugins.values()).build())
.build();
return merger.merge(model, lifecycleModel);
}
}
private void addPlugin(Map<Plugin, Plugin> plugins, Plugin plugin) {
Plugin cur = plugins.putIfAbsent(plugin, plugin);
if (cur != null) {
Map<String, PluginExecution> execs = new LinkedHashMap<>();
cur.getExecutions().forEach(e -> execs.put(e.getId(), e));
plugin.getExecutions().forEach(e -> {
int i = 0;
String id = e.getId();
while (execs.putIfAbsent(id, e.withId(id)) != null) {
id = e.getId() + "-" + (++i);
}
});
Plugin merged = cur.withExecutions(execs.values());
plugins.put(merged, merged);
}
}
private static String getExecutionId(Plugin plugin, String goal) {
Set<String> existingIds = plugin != null
? plugin.getExecutions().stream().map(PluginExecution::getId).collect(Collectors.toSet())
: Set.of();
String base = "default-" + goal;
String id = base;
for (int index = 1; existingIds.contains(id); index++) {
id = base + '-' + index;
}
return id;
}
/**
* The domain-specific model merger for lifecycle bindings
*/
protected static class LifecycleBindingsMerger extends MavenModelMerger {
private static final String PLUGIN_MANAGEMENT = "plugin-management";
public Model merge(Model target, Model source) {
Build targetBuild = target.getBuild();
if (targetBuild == null) {
targetBuild = Build.newInstance();
}
Map<Object, Object> context =
Collections.singletonMap(PLUGIN_MANAGEMENT, targetBuild.getPluginManagement());
Build.Builder builder = Build.newBuilder(targetBuild);
mergePluginContainer_Plugins(builder, targetBuild, source.getBuild(), false, context);
return target.withBuild(builder.build());
}
@SuppressWarnings({"checkstyle:methodname"})
@Override
protected void mergePluginContainer_Plugins(
PluginContainer.Builder builder,
PluginContainer target,
PluginContainer source,
boolean sourceDominant,
Map<Object, Object> context) {
List<Plugin> src = source.getPlugins();
if (!src.isEmpty()) {
List<Plugin> tgt = target.getPlugins();
Map<Object, Plugin> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
for (Plugin element : tgt) {
Object key = getPluginKey().apply(element);
merged.put(key, element);
}
Map<Object, Plugin> added = new LinkedHashMap<>();
for (Plugin element : src) {
Object key = getPluginKey().apply(element);
Plugin existing = merged.get(key);
if (existing != null) {
element = mergePlugin(existing, element, sourceDominant, context);
} else {
added.put(key, element);
}
merged.put(key, element);
}
if (!added.isEmpty()) {
PluginManagement pluginMgmt = (PluginManagement) context.get(PLUGIN_MANAGEMENT);
if (pluginMgmt != null) {
for (Plugin managedPlugin : pluginMgmt.getPlugins()) {
Object key = getPluginKey().apply(managedPlugin);
Plugin addedPlugin = added.get(key);
if (addedPlugin != null) {
Plugin plugin =
mergePlugin(managedPlugin, addedPlugin, sourceDominant, Collections.emptyMap());
merged.put(key, plugin);
}
}
}
}
List<Plugin> result = new ArrayList<>(merged.values());
builder.plugins(result);
}
}
@Override
protected void mergePluginExecution_Priority(
PluginExecution.Builder builder,
PluginExecution target,
PluginExecution source,
boolean sourceDominant,
Map<Object, Object> context) {
if (target.getPriority() > source.getPriority()) {
builder.priority(source.getPriority());
builder.location("priority", source.getLocation("priority"));
}
}
// mergePluginExecution_Priority( builder, target, source, sourceDominant, context );
}
}

View File

@ -0,0 +1,222 @@
/*
* 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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
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.ModelBuilderResult;
import org.apache.maven.api.services.ModelProblem;
/**
* Collects the output of the model builder.
*
*/
class DefaultModelBuilderResult implements ModelBuilderResult {
private Model fileModel;
private Model activatedFileModel;
private Model effectiveModel;
private List<String> modelIds;
private Map<String, Model> rawModels;
private Map<String, List<Profile>> activePomProfiles;
private List<Profile> activeExternalProfiles;
private List<ModelProblem> problems;
DefaultModelBuilderResult() {
modelIds = new ArrayList<>();
rawModels = new HashMap<>();
activePomProfiles = new HashMap<>();
activeExternalProfiles = new ArrayList<>();
problems = new ArrayList<>();
}
DefaultModelBuilderResult(ModelBuilderResult result) {
this();
this.activeExternalProfiles.addAll(result.getActiveExternalProfiles());
this.effectiveModel = result.getEffectiveModel();
this.fileModel = result.getFileModel();
this.problems.addAll(result.getProblems());
for (String modelId : result.getModelIds()) {
this.modelIds.add(modelId);
this.rawModels.put(modelId, result.getRawModel(modelId).orElseThrow());
this.activePomProfiles.put(modelId, result.getActivePomProfiles(modelId));
}
}
@Override
public Model getFileModel() {
return fileModel;
}
public DefaultModelBuilderResult setFileModel(Model fileModel) {
this.fileModel = fileModel;
return this;
}
public Model getActivatedFileModel() {
return activatedFileModel;
}
public DefaultModelBuilderResult setActivatedFileModel(Model activatedFileModel) {
this.activatedFileModel = activatedFileModel;
return this;
}
@Override
public Model getEffectiveModel() {
return effectiveModel;
}
public DefaultModelBuilderResult setEffectiveModel(Model model) {
this.effectiveModel = model;
return this;
}
@Override
public List<String> getModelIds() {
return modelIds;
}
public DefaultModelBuilderResult addModelId(String modelId) {
// Intentionally notNull because Super POM may not contain a modelId
Objects.requireNonNull(modelId, "modelId cannot be null");
modelIds.add(modelId);
return this;
}
@Override
public Model getRawModel() {
return rawModels.get(modelIds.get(0));
}
@Override
public Optional<Model> getRawModel(String modelId) {
return Optional.ofNullable(rawModels.get(modelId));
}
public DefaultModelBuilderResult setRawModel(String modelId, Model rawModel) {
// Intentionally notNull because Super POM may not contain a modelId
Objects.requireNonNull(modelId, "modelId cannot be null");
rawModels.put(modelId, rawModel);
return this;
}
@Override
public List<Profile> getActivePomProfiles(String modelId) {
List<Profile> profiles = activePomProfiles.get(modelId);
return profiles != null ? profiles : List.of();
}
public DefaultModelBuilderResult setActivePomProfiles(String modelId, List<Profile> activeProfiles) {
// Intentionally notNull because Super POM may not contain a modelId
Objects.requireNonNull(modelId, "modelId cannot be null");
if (activeProfiles != null) {
this.activePomProfiles.put(modelId, new ArrayList<>(activeProfiles));
} else {
this.activePomProfiles.remove(modelId);
}
return this;
}
@Override
public List<Profile> getActiveExternalProfiles() {
return activeExternalProfiles;
}
public DefaultModelBuilderResult setActiveExternalProfiles(List<Profile> activeProfiles) {
if (activeProfiles != null) {
this.activeExternalProfiles = new ArrayList<>(activeProfiles);
} else {
this.activeExternalProfiles.clear();
}
return this;
}
@Override
public List<ModelProblem> getProblems() {
return problems;
}
public DefaultModelBuilderResult setProblems(List<ModelProblem> problems) {
if (problems != null) {
this.problems = new ArrayList<>(problems);
} else {
this.problems.clear();
}
return this;
}
public String toString() {
if (!modelIds.isEmpty()) {
String modelId = modelIds.get(0);
StringBuilder sb = new StringBuilder();
sb.append(problems.size())
.append(
(problems.size() == 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) {
sb.append(System.lineSeparator());
sb.append(" - [");
sb.append(problem.getSeverity());
sb.append("] ");
sb.append(problem.getMessage());
String loc = Stream.of(
problem.getModelId().equals(modelId) ? problem.getModelId() : "",
problem.getModelId().equals(modelId) ? problem.getSource() : "",
problem.getLineNumber() > 0 ? "line " + problem.getLineNumber() : "",
problem.getColumnNumber() > 0 ? "column " + problem.getColumnNumber() : "")
.filter(s -> !s.isEmpty())
.collect(Collectors.joining(", "));
if (!loc.isEmpty()) {
sb.append(" @ ").append(loc);
}
}
return sb.toString();
}
return null;
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.function.Consumer;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.ModelBuildingEvent;
/**
* Holds data relevant for a model building event.
*/
record DefaultModelBuildingEvent(
Model model, Consumer<Model> update, ModelBuilderRequest request, ModelProblemCollector problems)
implements ModelBuildingEvent {}

View File

@ -0,0 +1,485 @@
/*
* 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.net.URI;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.Model;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblem;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.*;
import org.apache.maven.model.v4.MavenTransformer;
import org.codehaus.plexus.interpolation.AbstractDelegatingValueSource;
import org.codehaus.plexus.interpolation.AbstractValueSource;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
import org.codehaus.plexus.interpolation.MapBasedValueSource;
import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
import org.codehaus.plexus.interpolation.PrefixedValueSourceWrapper;
import org.codehaus.plexus.interpolation.QueryEnabledValueSource;
import org.codehaus.plexus.interpolation.RecursionInterceptor;
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
import org.codehaus.plexus.interpolation.ValueSource;
import org.codehaus.plexus.interpolation.reflection.ReflectionValueExtractor;
import org.codehaus.plexus.interpolation.util.ValueSourceUtils;
@Named
@Singleton
public class DefaultModelInterpolator implements ModelInterpolator {
private static final String PREFIX_PROJECT = "project.";
private static final String PREFIX_POM = "pom.";
private static final List<String> PROJECT_PREFIXES_3_1 = Arrays.asList(PREFIX_POM, PREFIX_PROJECT);
private static final List<String> PROJECT_PREFIXES_4_0 = Collections.singletonList(PREFIX_PROJECT);
private static final Collection<String> TRANSLATED_PATH_EXPRESSIONS;
static {
Collection<String> translatedPrefixes = new HashSet<>();
// MNG-1927, MNG-2124, MNG-3355:
// If the build section is present and the project directory is non-null, we should make
// sure interpolation of the directories below uses translated paths.
// Afterward, we'll double back and translate any paths that weren't covered during interpolation via the
// code below...
translatedPrefixes.add("build.directory");
translatedPrefixes.add("build.outputDirectory");
translatedPrefixes.add("build.testOutputDirectory");
translatedPrefixes.add("build.sourceDirectory");
translatedPrefixes.add("build.testSourceDirectory");
translatedPrefixes.add("build.scriptSourceDirectory");
translatedPrefixes.add("reporting.outputDirectory");
TRANSLATED_PATH_EXPRESSIONS = translatedPrefixes;
}
private final PathTranslator pathTranslator;
private final UrlNormalizer urlNormalizer;
private final RootLocator rootLocator;
@Inject
public DefaultModelInterpolator(
PathTranslator pathTranslator, UrlNormalizer urlNormalizer, RootLocator rootLocator) {
this.pathTranslator = pathTranslator;
this.urlNormalizer = urlNormalizer;
this.rootLocator = rootLocator;
}
interface InnerInterpolator {
String interpolate(String value);
}
@Override
public Model interpolateModel(
Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems) {
List<? extends ValueSource> valueSources = createValueSources(model, projectDir, request, problems);
List<? extends InterpolationPostProcessor> postProcessors = createPostProcessors(model, projectDir, request);
InnerInterpolator innerInterpolator = createInterpolator(valueSources, postProcessors, request, problems);
return new MavenTransformer(innerInterpolator::interpolate).visit(model);
}
private InnerInterpolator createInterpolator(
List<? extends ValueSource> valueSources,
List<? extends InterpolationPostProcessor> postProcessors,
ModelBuilderRequest request,
ModelProblemCollector problems) {
Map<String, String> cache = new HashMap<>();
StringSearchInterpolator interpolator = new StringSearchInterpolator();
interpolator.setCacheAnswers(true);
for (ValueSource vs : valueSources) {
interpolator.addValueSource(vs);
}
for (InterpolationPostProcessor postProcessor : postProcessors) {
interpolator.addPostProcessor(postProcessor);
}
RecursionInterceptor recursionInterceptor = createRecursionInterceptor(request);
return value -> {
if (value != null && value.contains("${")) {
String c = cache.get(value);
if (c == null) {
try {
c = interpolator.interpolate(value, recursionInterceptor);
} catch (InterpolationException e) {
problems.add(BuilderProblem.Severity.ERROR, ModelProblem.Version.BASE, e.getMessage(), e);
}
cache.put(value, c);
}
return c;
}
return value;
};
}
protected List<String> getProjectPrefixes(ModelBuilderRequest request) {
return request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_4_0
? PROJECT_PREFIXES_4_0
: PROJECT_PREFIXES_3_1;
}
protected List<ValueSource> createValueSources(
Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems) {
Map<String, String> modelProperties = model.getProperties();
ValueSource projectPrefixValueSource;
ValueSource prefixlessObjectBasedValueSource;
if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_4_0) {
projectPrefixValueSource = new PrefixedObjectValueSource(PROJECT_PREFIXES_4_0, model, false);
prefixlessObjectBasedValueSource = new ObjectBasedValueSource(model);
} else {
projectPrefixValueSource = new PrefixedObjectValueSource(PROJECT_PREFIXES_3_1, model, false);
if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0) {
projectPrefixValueSource =
new ProblemDetectingValueSource(projectPrefixValueSource, PREFIX_POM, PREFIX_PROJECT, problems);
}
prefixlessObjectBasedValueSource = new ObjectBasedValueSource(model);
if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0) {
prefixlessObjectBasedValueSource =
new ProblemDetectingValueSource(prefixlessObjectBasedValueSource, "", PREFIX_PROJECT, problems);
}
}
// NOTE: Order counts here!
List<ValueSource> valueSources = new ArrayList<>(9);
if (projectDir != null) {
ValueSource basedirValueSource = new PrefixedValueSourceWrapper(
new AbstractValueSource(false) {
@Override
public Object getValue(String expression) {
if ("basedir".equals(expression)) {
return projectDir.toAbsolutePath().toString();
} else if (expression.startsWith("basedir.")) {
Path basedir = projectDir.toAbsolutePath();
return new ObjectBasedValueSource(basedir)
.getValue(expression.substring("basedir.".length()));
}
return null;
}
},
getProjectPrefixes(request),
true);
valueSources.add(basedirValueSource);
ValueSource baseUriValueSource = new PrefixedValueSourceWrapper(
new AbstractValueSource(false) {
@Override
public Object getValue(String expression) {
if ("baseUri".equals(expression)) {
return projectDir.toAbsolutePath().toUri().toASCIIString();
} else if (expression.startsWith("baseUri.")) {
URI baseUri = projectDir.toAbsolutePath().toUri();
return new ObjectBasedValueSource(baseUri)
.getValue(expression.substring("baseUri.".length()));
}
return null;
}
},
getProjectPrefixes(request),
false);
valueSources.add(baseUriValueSource);
valueSources.add(new BuildTimestampValueSource(request.getSession().getStartTime(), modelProperties));
}
valueSources.add(new PrefixedValueSourceWrapper(
new AbstractValueSource(false) {
@Override
public Object getValue(String expression) {
if ("rootDirectory".equals(expression)) {
Path root = rootLocator.findMandatoryRoot(projectDir);
return root.toFile().getPath();
} else if (expression.startsWith("rootDirectory.")) {
Path root = rootLocator.findMandatoryRoot(projectDir);
return new ObjectBasedValueSource(root)
.getValue(expression.substring("rootDirectory.".length()));
}
return null;
}
},
getProjectPrefixes(request)));
valueSources.add(projectPrefixValueSource);
valueSources.add(new MapBasedValueSource(request.getUserProperties()));
valueSources.add(new MapBasedValueSource(modelProperties));
valueSources.add(new MapBasedValueSource(request.getSystemProperties()));
valueSources.add(new AbstractValueSource(false) {
@Override
public Object getValue(String expression) {
return request.getSystemProperties().get("env." + expression);
}
});
valueSources.add(prefixlessObjectBasedValueSource);
return valueSources;
}
protected List<? extends InterpolationPostProcessor> createPostProcessors(
Model model, Path projectDir, ModelBuilderRequest request) {
List<InterpolationPostProcessor> processors = new ArrayList<>(2);
if (projectDir != null) {
processors.add(new PathTranslatingPostProcessor(
getProjectPrefixes(request), TRANSLATED_PATH_EXPRESSIONS, projectDir, pathTranslator));
}
processors.add(new UrlNormalizingPostProcessor(urlNormalizer));
return processors;
}
protected RecursionInterceptor createRecursionInterceptor(ModelBuilderRequest request) {
return new PrefixAwareRecursionInterceptor(getProjectPrefixes(request));
}
static class PathTranslatingPostProcessor implements InterpolationPostProcessor {
private final Collection<String> unprefixedPathKeys;
private final Path projectDir;
private final PathTranslator pathTranslator;
private final List<String> expressionPrefixes;
PathTranslatingPostProcessor(
List<String> expressionPrefixes,
Collection<String> unprefixedPathKeys,
Path projectDir,
PathTranslator pathTranslator) {
this.expressionPrefixes = expressionPrefixes;
this.unprefixedPathKeys = unprefixedPathKeys;
this.projectDir = projectDir;
this.pathTranslator = pathTranslator;
}
@Override
public Object execute(String expression, Object value) {
if (value != null) {
expression = ValueSourceUtils.trimPrefix(expression, expressionPrefixes, true);
if (unprefixedPathKeys.contains(expression)) {
return pathTranslator.alignToBaseDirectory(String.valueOf(value), projectDir);
}
}
return null;
}
}
/**
* Ensures that expressions referring to URLs evaluate to normalized URLs.
*
*/
static class UrlNormalizingPostProcessor implements InterpolationPostProcessor {
private static final Set<String> URL_EXPRESSIONS;
static {
Set<String> expressions = new HashSet<>();
expressions.add("project.url");
expressions.add("project.scm.url");
expressions.add("project.scm.connection");
expressions.add("project.scm.developerConnection");
expressions.add("project.distributionManagement.site.url");
URL_EXPRESSIONS = expressions;
}
private final UrlNormalizer normalizer;
UrlNormalizingPostProcessor(UrlNormalizer normalizer) {
this.normalizer = normalizer;
}
@Override
public Object execute(String expression, Object value) {
if (value != null && URL_EXPRESSIONS.contains(expression)) {
return normalizer.normalize(value.toString());
}
return null;
}
}
/**
* Wraps an arbitrary object with an {@link ObjectBasedValueSource} instance, then
* wraps that source with a {@link PrefixedValueSourceWrapper} instance, to which
* this class delegates all of its calls.
*/
public static class PrefixedObjectValueSource extends AbstractDelegatingValueSource
implements QueryEnabledValueSource {
/**
* Wrap the specified root object, allowing the specified expression prefix.
* @param prefix the prefix.
* @param root the root of the graph.
*/
public PrefixedObjectValueSource(String prefix, Object root) {
super(new PrefixedValueSourceWrapper(new ObjectBasedValueSource(root), prefix));
}
/**
* Wrap the specified root object, allowing the specified list of expression
* prefixes and setting whether the {@link PrefixedValueSourceWrapper} allows
* unprefixed expressions.
* @param possiblePrefixes The possible prefixes.
* @param root The root of the graph.
* @param allowUnprefixedExpressions if we allow undefined expressions or not.
*/
public PrefixedObjectValueSource(
List<String> possiblePrefixes, Object root, boolean allowUnprefixedExpressions) {
super(new PrefixedValueSourceWrapper(
new ObjectBasedValueSource(root), possiblePrefixes, allowUnprefixedExpressions));
}
/**
* {@inheritDoc}
*/
public String getLastExpression() {
return ((QueryEnabledValueSource) getDelegate()).getLastExpression();
}
}
/**
* Wraps an object, providing reflective access to the object graph of which the
* supplied object is the root. Expressions like 'child.name' will translate into
* 'rootObject.getChild().getName()' for non-boolean properties, and
* 'rootObject.getChild().isName()' for boolean properties.
*/
public static class ObjectBasedValueSource extends AbstractValueSource {
private final Object root;
/**
* Construct a new value source, using the supplied object as the root from
* which to start, and using expressions split at the dot ('.') to navigate
* the object graph beneath this root.
* @param root the root of the graph.
*/
public ObjectBasedValueSource(Object root) {
super(true);
this.root = root;
}
/**
* <p>Split the expression into parts, tokenized on the dot ('.') character. Then,
* starting at the root object contained in this value source, apply each part
* to the object graph below this root, using either 'getXXX()' or 'isXXX()'
* accessor types to resolve the value for each successive expression part.
* Finally, return the result of the last expression part's resolution.</p>
*
* <p><b>NOTE:</b> The object-graph nagivation actually takes place via the
* {@link ReflectionValueExtractor} class.</p>
*/
public Object getValue(String expression) {
if (expression == null || expression.trim().isEmpty()) {
return null;
}
try {
return ReflectionValueExtractor.evaluate(expression, root, false);
} catch (Exception e) {
addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
}
return null;
}
}
/**
* Wraps another value source and intercepts interpolated expressions, checking for problems.
*
*/
static class ProblemDetectingValueSource implements ValueSource {
private final ValueSource valueSource;
private final String bannedPrefix;
private final String newPrefix;
private final ModelProblemCollector problems;
ProblemDetectingValueSource(
ValueSource valueSource, String bannedPrefix, String newPrefix, ModelProblemCollector problems) {
this.valueSource = valueSource;
this.bannedPrefix = bannedPrefix;
this.newPrefix = newPrefix;
this.problems = problems;
}
@Override
public Object getValue(String expression) {
Object value = valueSource.getValue(expression);
if (value != null && expression.startsWith(bannedPrefix)) {
String msg = "The expression ${" + expression + "} is deprecated.";
if (newPrefix != null && !newPrefix.isEmpty()) {
msg += " Please use ${" + newPrefix + expression.substring(bannedPrefix.length()) + "} instead.";
}
problems.add(BuilderProblem.Severity.WARNING, ModelProblem.Version.V20, msg);
}
return value;
}
@Override
public List getFeedback() {
return valueSource.getFeedback();
}
@Override
public void clearFeedback() {
valueSource.clearFeedback();
}
}
static class BuildTimestampValueSource extends AbstractValueSource {
private final Instant startTime;
private final Map<String, String> properties;
BuildTimestampValueSource(Instant startTime, Map<String, String> properties) {
super(false);
this.startTime = startTime;
this.properties = properties;
}
@Override
public Object getValue(String expression) {
if ("build.timestamp".equals(expression) || "maven.build.timestamp".equals(expression)) {
return new MavenBuildTimestamp(startTime, properties).formattedTimestamp();
}
return null;
}
}
}

View File

@ -0,0 +1,147 @@
/*
* 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.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.model.Build;
import org.apache.maven.api.model.Dependency;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Plugin;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.*;
/**
* Handles normalization of a model.
*
*/
@Named
@Singleton
public class DefaultModelNormalizer implements ModelNormalizer {
private DuplicateMerger merger = new DuplicateMerger();
@Override
public Model mergeDuplicates(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
Model.Builder builder = Model.newBuilder(model);
Build build = model.getBuild();
if (build != null) {
List<Plugin> plugins = build.getPlugins();
Map<Object, Plugin> normalized = new LinkedHashMap<>(plugins.size() * 2);
for (Plugin plugin : plugins) {
Object key = plugin.getKey();
Plugin first = normalized.get(key);
if (first != null) {
plugin = merger.mergePlugin(plugin, first);
}
normalized.put(key, plugin);
}
if (plugins.size() != normalized.size()) {
builder.build(
Build.newBuilder(build).plugins(normalized.values()).build());
}
}
/*
* NOTE: This is primarily to keep backward-compat with Maven 2.x which did not validate that dependencies are
* unique within a single POM. Upon multiple declarations, 2.x just kept the last one but retained the order of
* the first occurrence. So when we're in lenient/compat mode, we have to deal with such broken POMs and mimic
* the way 2.x works. When we're in strict mode, the removal of duplicates just saves other merging steps from
* aftereffects and bogus error messages.
*/
List<Dependency> dependencies = model.getDependencies();
Map<String, Dependency> normalized = new LinkedHashMap<>(dependencies.size() * 2);
for (Dependency dependency : dependencies) {
normalized.put(dependency.getManagementKey(), dependency);
}
if (dependencies.size() != normalized.size()) {
builder.dependencies(normalized.values());
}
return builder.build();
}
/**
* DuplicateMerger
*/
protected static class DuplicateMerger extends MavenModelMerger {
public Plugin mergePlugin(Plugin target, Plugin source) {
return super.mergePlugin(target, source, false, Collections.emptyMap());
}
}
@Override
public Model injectDefaultValues(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
Model.Builder builder = Model.newBuilder(model);
builder.dependencies(injectList(model.getDependencies(), this::injectDependency));
Build build = model.getBuild();
if (build != null) {
Build newBuild = Build.newBuilder(build)
.plugins(injectList(build.getPlugins(), this::injectPlugin))
.build();
builder.build(newBuild != build ? newBuild : null);
}
return builder.build();
}
private Plugin injectPlugin(Plugin p) {
return Plugin.newBuilder(p)
.dependencies(injectList(p.getDependencies(), this::injectDependency))
.build();
}
private Dependency injectDependency(Dependency d) {
// we cannot set this directly in the MDO due to the interactions with dependency management
return (d.getScope() == null || d.getScope().isEmpty()) ? d.withScope("compile") : d;
}
/**
* Returns a list suited for the builders, i.e. null if not modified
*/
private <T> List<T> injectList(List<T> list, Function<T, T> modifer) {
List<T> newList = null;
for (int i = 0; i < list.size(); i++) {
T oldT = list.get(i);
T newT = modifer.apply(oldT);
if (newT != oldT) {
if (newList == null) {
newList = new ArrayList<>(list);
}
newList.set(i, newT);
}
}
return newList;
}
}

View File

@ -0,0 +1,121 @@
/*
* 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.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
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.Build;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Reporting;
import org.apache.maven.api.model.Resource;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.model.*;
/**
* Resolves relative paths within a model against a specific base directory.
*
*/
@Named
@Singleton
public class DefaultModelPathTranslator implements ModelPathTranslator {
private final PathTranslator pathTranslator;
@Inject
public DefaultModelPathTranslator(PathTranslator pathTranslator) {
this.pathTranslator = pathTranslator;
}
@Override
public Model alignToBaseDirectory(Model model, Path basedir, ModelBuilderRequest request) {
if (model == null || basedir == null) {
return model;
}
Build build = model.getBuild();
Build newBuild = null;
if (build != null) {
newBuild = Build.newBuilder(build)
.directory(alignToBaseDirectory(build.getDirectory(), basedir))
.sourceDirectory(alignToBaseDirectory(build.getSourceDirectory(), basedir))
.testSourceDirectory(alignToBaseDirectory(build.getTestSourceDirectory(), basedir))
.scriptSourceDirectory(alignToBaseDirectory(build.getScriptSourceDirectory(), basedir))
.resources(map(build.getResources(), r -> alignToBaseDirectory(r, basedir)))
.testResources(map(build.getTestResources(), r -> alignToBaseDirectory(r, basedir)))
.filters(map(build.getFilters(), s -> alignToBaseDirectory(s, basedir)))
.outputDirectory(alignToBaseDirectory(build.getOutputDirectory(), basedir))
.testOutputDirectory(alignToBaseDirectory(build.getTestOutputDirectory(), basedir))
.build();
}
Reporting reporting = model.getReporting();
Reporting newReporting = null;
if (reporting != null) {
newReporting = Reporting.newBuilder(reporting)
.outputDirectory(alignToBaseDirectory(reporting.getOutputDirectory(), basedir))
.build();
}
if (newBuild != build || newReporting != reporting) {
model = Model.newBuilder(model)
.build(newBuild)
.reporting(newReporting)
.build();
}
return model;
}
private <T> List<T> map(List<T> resources, Function<T, T> mapper) {
List<T> newResources = null;
if (resources != null) {
for (int i = 0; i < resources.size(); i++) {
T resource = resources.get(i);
T newResource = mapper.apply(resource);
if (newResource != null) {
if (newResources == null) {
newResources = new ArrayList<>(resources);
}
newResources.set(i, newResource);
}
}
}
return newResources;
}
private Resource alignToBaseDirectory(Resource resource, Path basedir) {
if (resource != null) {
String newDir = alignToBaseDirectory(resource.getDirectory(), basedir);
if (newDir != null) {
return resource.withDirectory(newDir);
}
}
return resource;
}
private String alignToBaseDirectory(String path, Path basedir) {
String newPath = pathTranslator.alignToBaseDirectory(path, basedir);
return Objects.equals(path, newPath) ? null : newPath;
}
}

View File

@ -0,0 +1,182 @@
/*
* 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 org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelProblem;
/**
* Describes a problem that was encountered during model building. A problem can either be an exception that was thrown
* or a simple string message. In addition, a problem carries a hint about its source, e.g. the POM file that exhibits
* the problem.
*
*/
public class DefaultModelProblem implements ModelProblem {
private final String source;
private final int lineNumber;
private final int columnNumber;
private final String modelId;
private final String message;
private final Exception exception;
private final Severity severity;
private final Version version;
/**
* Creates a new problem with the specified message and exception.
*
* @param message The message describing the problem, may be {@code null}.
* @param severity The severity level of the problem, may be {@code null} to default to
* {@link Severity#ERROR}.
* @param source The source of the problem, may be {@code null}.
* @param lineNumber The one-based index of the line containing the error or {@code -1} if unknown.
* @param columnNumber The one-based index of the column containing the error or {@code -1} if unknown.
* @param exception The exception that caused this problem, may be {@code null}.
*/
// mkleint: does this need to be public?
public DefaultModelProblem(
String message,
Severity severity,
Version version,
Model source,
int lineNumber,
int columnNumber,
Exception exception) {
this(
message,
severity,
version,
ModelProblemUtils.toPath(source),
lineNumber,
columnNumber,
ModelProblemUtils.toId(source),
exception);
}
/**
* Creates a new problem with the specified message and exception.
*
* @param message The message describing the problem, may be {@code null}.
* @param severity The severity level of the problem, may be {@code null} to default to
* {@link Severity#ERROR}.
* @param version The version since the problem is relevant
* @param source A hint about the source of the problem like a file path, may be {@code null}.
* @param lineNumber The one-based index of the line containing the problem or {@code -1} if unknown.
* @param columnNumber The one-based index of the column containing the problem or {@code -1} if unknown.
* @param modelId The identifier of the model that exhibits the problem, may be {@code null}.
* @param exception The exception that caused this problem, may be {@code null}.
*/
// mkleint: does this need to be public?
@SuppressWarnings("checkstyle:parameternumber")
public DefaultModelProblem(
String message,
Severity severity,
Version version,
String source,
int lineNumber,
int columnNumber,
String modelId,
Exception exception) {
this.message = message;
this.severity = (severity != null) ? severity : Severity.ERROR;
this.source = (source != null) ? source : "";
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
this.modelId = (modelId != null) ? modelId : "";
this.exception = exception;
this.version = version;
}
@Override
public String getSource() {
return source;
}
@Override
public int getLineNumber() {
return lineNumber;
}
@Override
public int getColumnNumber() {
return columnNumber;
}
public String getModelId() {
return modelId;
}
@Override
public Exception getException() {
return exception;
}
@Override
public String getLocation() {
return "";
}
@Override
public String getMessage() {
String msg = null;
if (message != null && !message.isEmpty()) {
msg = message;
} else if (exception != null) {
msg = exception.getMessage();
}
if (msg == null) {
msg = "";
}
return msg;
}
@Override
public Severity getSeverity() {
return severity;
}
public Version getVersion() {
return version;
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder(128);
buffer.append('[').append(getSeverity()).append("] ");
buffer.append(getMessage());
String location = ModelProblemUtils.formatLocation(this, null);
if (!location.isEmpty()) {
buffer.append(" @ ");
buffer.append(location);
}
return buffer.toString();
}
}

View File

@ -0,0 +1,198 @@
/*
* 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.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import org.apache.maven.api.model.InputLocation;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ModelBuilderException;
import org.apache.maven.api.services.ModelBuilderResult;
import org.apache.maven.api.services.ModelProblem;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.spi.ModelParserException;
/**
* Collects problems that are encountered during model building. The primary purpose of this component is to account for
* the fact that the problem reporter has/should not have information about the calling context and hence cannot provide
* an expressive source hint for the model problem. Instead, the source hint is configured by the model builder before
* it delegates to other components that potentially encounter problems. Then, the problem reporter can focus on
* providing a simple error message, leaving the donkey work of creating a nice model problem to this component.
*
*/
class DefaultModelProblemCollector implements ModelProblemCollector {
private final ModelBuilderResult result;
private List<ModelProblem> problems;
private String source;
private Model sourceModel;
private Model rootModel;
private Set<ModelProblem.Severity> severities = EnumSet.noneOf(ModelProblem.Severity.class);
DefaultModelProblemCollector(ModelBuilderResult result) {
this.result = result;
this.problems = result.getProblems();
for (ModelProblem problem : this.problems) {
severities.add(problem.getSeverity());
}
}
public boolean hasFatalErrors() {
return severities.contains(ModelProblem.Severity.FATAL);
}
public boolean hasErrors() {
return severities.contains(ModelProblem.Severity.ERROR) || severities.contains(ModelProblem.Severity.FATAL);
}
@Override
public List<ModelProblem> getProblems() {
return problems;
}
public void setSource(String source) {
this.source = source;
this.sourceModel = null;
}
public void setSource(Model source) {
this.sourceModel = source;
this.source = null;
if (rootModel == null) {
rootModel = source;
}
}
private String getSource() {
if (source == null && sourceModel != null) {
source = ModelProblemUtils.toPath(sourceModel);
}
return source;
}
private String getModelId() {
return ModelProblemUtils.toId(sourceModel);
}
public void setRootModel(Model rootModel) {
this.rootModel = rootModel;
}
public Model getRootModel() {
return rootModel;
}
public String getRootModelId() {
return ModelProblemUtils.toId(rootModel);
}
@Override
public void add(ModelProblem problem) {
problems.add(problem);
severities.add(problem.getSeverity());
}
public void addAll(Collection<ModelProblem> problems) {
this.problems.addAll(problems);
for (ModelProblem problem : problems) {
severities.add(problem.getSeverity());
}
}
@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,
String message,
InputLocation location,
Exception exception) {
int line = -1;
int column = -1;
String source = null;
String modelId = null;
if (location != null) {
line = location.getLineNumber();
column = location.getColumnNumber();
if (location.getSource() != null) {
modelId = location.getSource().getModelId();
source = location.getSource().getLocation();
}
}
if (modelId == null) {
modelId = getModelId();
source = getSource();
}
if (line <= 0 && column <= 0 && exception instanceof ModelParserException e) {
line = e.getLineNumber();
column = e.getColumnNumber();
}
ModelProblem problem =
new DefaultModelProblem(message, severity, version, source, line, column, modelId, exception);
add(problem);
}
public ModelBuilderException newModelBuilderException() {
ModelBuilderResult result = this.result;
if (result.getModelIds().isEmpty()) {
DefaultModelBuilderResult tmp = new DefaultModelBuilderResult();
tmp.setEffectiveModel(result.getEffectiveModel());
tmp.setProblems(getProblems());
tmp.setActiveExternalProfiles(result.getActiveExternalProfiles());
String id = getRootModelId();
tmp.addModelId(id);
tmp.setRawModel(id, getRootModel());
result = tmp;
}
return new ModelBuilderException(result);
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.io.IOException;
import java.nio.file.Files;
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.Objects;
import java.util.Optional;
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.Model;
import org.apache.maven.api.services.model.*;
import org.apache.maven.api.services.xml.ModelXmlFactory;
import org.apache.maven.api.services.xml.XmlReaderRequest;
import org.apache.maven.api.spi.ModelParser;
import org.apache.maven.api.spi.ModelParserException;
/**
*
* Note: uses @Typed to limit the types it is available for injection to just ModelProcessor.
*
* This is because the ModelProcessor interface extends ModelLocator and ModelReader. If we
* made this component available under all its interfaces then it could end up being injected
* into itself leading to a stack overflow.
*
* A side effect of using @Typed is that it translates to explicit bindings in the container.
* So instead of binding the component under a 'wildcard' key it is now bound with an explicit
* key. Since this is a default component this will be a plain binding of ModelProcessor to
* this implementation type, ie. no hint/name.
*
* This leads to a second side effect in that any @Inject request for just ModelProcessor in
* the same injector is immediately matched to this explicit binding, which means extensions
* cannot override this binding. This is because the lookup is always short-circuited in this
* specific situation (plain @Inject request, and plain explicit binding for the same type.)
*
* The simplest solution is to use a custom @Named here so it isn't bound under the plain key.
* This is only necessary for default components using @Typed that want to support overriding.
*
* As a non-default component this now gets a negative priority relative to other implementations
* of the same interface. Since we want to allow overriding this doesn't matter in this case.
* (if it did we could add @Priority of 0 to match the priority given to default components.)
*/
@Named
@Singleton
public class DefaultModelProcessor implements ModelProcessor {
private final ModelXmlFactory modelXmlFactory;
private final List<ModelParser> modelParsers;
@Inject
public DefaultModelProcessor(ModelXmlFactory modelXmlFactory, List<ModelParser> modelParsers) {
this.modelXmlFactory = modelXmlFactory;
this.modelParsers = modelParsers;
}
@Override
public Path locateExistingPom(Path projectDirectory) {
// Note that the ModelProcessor#locatePom never returns null
// while the ModelParser#locatePom needs to return an existing path!
Path pom = modelParsers.stream()
.map(m -> m.locate(projectDirectory)
.map(org.apache.maven.api.services.Source::getPath)
.orElse(null))
.filter(Objects::nonNull)
.findFirst()
.orElseGet(() -> doLocateExistingPom(projectDirectory));
if (pom != null && !pom.equals(projectDirectory) && !pom.getParent().equals(projectDirectory)) {
throw new IllegalArgumentException("The POM found does not belong to the given directory: " + pom);
}
return pom;
}
@Override
public Model read(XmlReaderRequest request) throws IOException {
Objects.requireNonNull(request, "source cannot be null");
Path pomFile = request.getPath();
if (pomFile != null) {
Path projectDirectory = pomFile.getParent();
List<ModelParserException> exceptions = new ArrayList<>();
for (ModelParser parser : modelParsers) {
try {
Optional<Model> model =
parser.locateAndParse(projectDirectory, Map.of(ModelParser.STRICT, request.isStrict()));
if (model.isPresent()) {
return model.get().withPomFile(pomFile);
}
} catch (ModelParserException e) {
exceptions.add(e);
}
}
try {
return doRead(request);
} catch (IOException e) {
exceptions.forEach(e::addSuppressed);
throw e;
}
} else {
return doRead(request);
}
}
private Path doLocateExistingPom(Path project) {
if (project == null) {
project = Paths.get(System.getProperty("user.dir"));
}
if (Files.isDirectory(project)) {
Path pom = project.resolve("pom.xml");
return Files.isRegularFile(pom) ? pom : null;
} else if (Files.isRegularFile(project)) {
return project;
} else {
return null;
}
}
private Model doRead(XmlReaderRequest request) throws IOException {
return modelXmlFactory.read(request);
}
}

View File

@ -0,0 +1,141 @@
/*
* 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.nio.file.Path;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelTransformerContext;
import org.apache.maven.api.services.model.*;
/**
*
* @since 4.0.0
*/
class DefaultModelTransformerContext implements ModelTransformerContext {
final ModelProcessor modelLocator;
final Map<String, String> userProperties = new ConcurrentHashMap<>();
final Map<Path, Holder> modelByPath = new ConcurrentHashMap<>();
final Map<GAKey, Holder> modelByGA = new ConcurrentHashMap<>();
public static class Holder {
private volatile boolean set;
private volatile Model model;
Holder() {}
Holder(Model model) {
this.model = Objects.requireNonNull(model);
this.set = true;
}
public static Model deref(Holder holder) {
return holder != null ? holder.get() : null;
}
public Model get() {
if (!set) {
synchronized (this) {
if (!set) {
try {
this.wait();
} catch (InterruptedException e) {
// Ignore
}
}
}
}
return model;
}
public Model computeIfAbsent(Supplier<Model> supplier) {
if (!set) {
synchronized (this) {
if (!set) {
this.set = true;
this.model = supplier.get();
this.notifyAll();
}
}
}
return model;
}
}
DefaultModelTransformerContext(ModelProcessor modelLocator) {
this.modelLocator = modelLocator;
}
@Override
public String getUserProperty(String key) {
return userProperties.get(key);
}
@Override
public Model getRawModel(Path from, Path p) {
return Holder.deref(modelByPath.get(p));
}
@Override
public Model getRawModel(Path from, String groupId, String artifactId) {
return Holder.deref(modelByGA.get(new GAKey(groupId, artifactId)));
}
@Override
public Path locate(Path path) {
return modelLocator.locateExistingPom(path);
}
static class GAKey {
private final String groupId;
private final String artifactId;
private final int hashCode;
GAKey(String groupId, String artifactId) {
this.groupId = groupId;
this.artifactId = artifactId;
this.hashCode = Objects.hash(groupId, artifactId);
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof GAKey)) {
return false;
}
GAKey other = (GAKey) obj;
return Objects.equals(artifactId, other.artifactId) && Objects.equals(groupId, other.groupId);
}
}
}

View File

@ -0,0 +1,231 @@
/*
* 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.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelBuilderException;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblem;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.ModelSource;
import org.apache.maven.api.services.ModelTransformerContext;
import org.apache.maven.api.services.ModelTransformerContextBuilder;
import org.apache.maven.internal.impl.model.DefaultModelTransformerContext.GAKey;
import org.apache.maven.internal.impl.model.DefaultModelTransformerContext.Holder;
/**
* Builds up the transformer context.
* After the buildplan is ready, the build()-method returns the immutable context useful during distribution.
* This is an inner class, as it must be able to call readRawModel()
*
* @since 4.0.0
*/
class DefaultModelTransformerContextBuilder implements ModelTransformerContextBuilder {
private final Graph dag = new Graph();
private final DefaultModelBuilder defaultModelBuilder;
private final DefaultModelTransformerContext context;
private final Map<String, Set<ModelSource>> mappedSources = new ConcurrentHashMap<>(64);
private volatile boolean fullReactorLoaded;
DefaultModelTransformerContextBuilder(DefaultModelBuilder defaultModelBuilder) {
this.defaultModelBuilder = defaultModelBuilder;
this.context = new DefaultModelTransformerContext(defaultModelBuilder.getModelProcessor());
}
/**
* If an interface could be extracted, DefaultModelProblemCollector should be ModelProblemCollectorExt
*/
@Override
public ModelTransformerContext initialize(ModelBuilderRequest request, ModelProblemCollector collector) {
// We must assume the TransformerContext was created using this.newTransformerContextBuilder()
DefaultModelProblemCollector problems = (DefaultModelProblemCollector) collector;
return new ModelTransformerContext() {
@Override
public Path locate(Path path) {
return context.locate(path);
}
@Override
public String getUserProperty(String key) {
return context.userProperties.computeIfAbsent(
key, k -> request.getUserProperties().get(key));
}
@Override
public Model getRawModel(Path from, String gId, String aId) {
Model model = findRawModel(from, gId, aId);
if (model != null) {
context.modelByGA.put(new GAKey(gId, aId), new Holder(model));
context.modelByPath.put(model.getPomFile(), new Holder(model));
}
return model;
}
@Override
public Model getRawModel(Path from, Path path) {
Model model = findRawModel(from, path);
if (model != null) {
String groupId = DefaultModelBuilder.getGroupId(model);
context.modelByGA.put(
new DefaultModelTransformerContext.GAKey(groupId, model.getArtifactId()),
new Holder(model));
context.modelByPath.put(path, new Holder(model));
}
return model;
}
private Model findRawModel(Path from, String groupId, String artifactId) {
ModelSource source = getSource(groupId, artifactId);
if (source == null) {
// we need to check the whole reactor in case it's a dependency
loadFullReactor();
source = getSource(groupId, artifactId);
}
if (source != null) {
if (!addEdge(from, source.getPath(), problems)) {
return null;
}
try {
ModelBuilderRequest gaBuildingRequest = ModelBuilderRequest.build(request, source);
return defaultModelBuilder.readRawModel(gaBuildingRequest, problems);
} catch (ModelBuilderException e) {
// gathered with problem collector
}
}
return null;
}
private void loadFullReactor() {
if (!fullReactorLoaded) {
synchronized (DefaultModelTransformerContextBuilder.this) {
if (!fullReactorLoaded) {
doLoadFullReactor();
fullReactorLoaded = true;
}
}
}
}
private void doLoadFullReactor() {
Path rootDirectory;
try {
rootDirectory = request.getSession().getRootDirectory();
} catch (IllegalStateException e) {
// if no root directory, bail out
return;
}
List<Path> toLoad = new ArrayList<>();
Path root = defaultModelBuilder.getModelProcessor().locateExistingPom(rootDirectory);
toLoad.add(root);
while (!toLoad.isEmpty()) {
Path pom = toLoad.remove(0);
try {
ModelBuilderRequest gaBuildingRequest =
ModelBuilderRequest.build(request, ModelSource.fromPath(pom));
Model rawModel = defaultModelBuilder.readFileModel(gaBuildingRequest, problems);
for (String module : rawModel.getModules()) {
Path moduleFile = defaultModelBuilder
.getModelProcessor()
.locateExistingPom(pom.getParent().resolve(module));
if (moduleFile != null) {
toLoad.add(moduleFile);
}
}
} catch (ModelBuilderException e) {
// gathered with problem collector
}
}
}
private Model findRawModel(Path from, Path p) {
if (!Files.isRegularFile(p)) {
throw new IllegalArgumentException("Not a regular file: " + p);
}
if (!addEdge(from, p, problems)) {
return null;
}
ModelBuilderRequest req = ModelBuilderRequest.build(request, ModelSource.fromPath(p));
try {
return defaultModelBuilder.readRawModel(req, problems);
} catch (ModelBuilderException e) {
// gathered with problem collector
}
return null;
}
};
}
private boolean addEdge(Path from, Path p, DefaultModelProblemCollector problems) {
try {
dag.addEdge(from.toString(), p.toString());
return true;
} catch (Graph.CycleDetectedException e) {
problems.add(new DefaultModelProblem(
"Cycle detected between models at " + from + " and " + p,
ModelProblem.Severity.FATAL,
null,
null,
0,
0,
null,
e));
return false;
}
}
@Override
public ModelTransformerContext build() {
return context;
}
public ModelSource getSource(String groupId, String artifactId) {
Set<ModelSource> sources = mappedSources.get(groupId + ":" + artifactId);
if (sources == null) {
return null;
}
return sources.stream()
.reduce((a, b) -> {
throw new IllegalStateException(String.format(
"No unique Source for %s:%s: %s and %s",
groupId, artifactId, a.getLocation(), b.getLocation()));
})
.orElse(null);
}
public void putSource(String groupId, String artifactId, ModelSource source) {
mappedSources
.computeIfAbsent(groupId + ":" + artifactId, k -> new HashSet<>())
.add(source);
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.Map;
import java.util.Properties;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.model.*;
/**
* Maven default implementation of the {@link ModelVersionProcessor} to support
* <a href="https://maven.apache.org/maven-ci-friendly.html">CI Friendly Versions</a>
*/
@Named
@Singleton
public class DefaultModelVersionProcessor implements ModelVersionProcessor {
private static final String SHA1_PROPERTY = "sha1";
private static final String CHANGELIST_PROPERTY = "changelist";
private static final String REVISION_PROPERTY = "revision";
@Override
public boolean isValidProperty(String property) {
return REVISION_PROPERTY.equals(property)
|| CHANGELIST_PROPERTY.equals(property)
|| SHA1_PROPERTY.equals(property);
}
@Override
public void overwriteModelProperties(Properties modelProperties, ModelBuilderRequest request) {
Map<String, String> props = request.getUserProperties();
if (props.containsKey(REVISION_PROPERTY)) {
modelProperties.put(REVISION_PROPERTY, props.get(REVISION_PROPERTY));
}
if (props.containsKey(CHANGELIST_PROPERTY)) {
modelProperties.put(CHANGELIST_PROPERTY, props.get(CHANGELIST_PROPERTY));
}
if (props.containsKey(SHA1_PROPERTY)) {
modelProperties.put(SHA1_PROPERTY, props.get(SHA1_PROPERTY));
}
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.io.File;
import java.nio.file.Path;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.services.model.*;
/**
* Resolves relative paths against a specific base directory.
*
*/
@Named
@Singleton
public class DefaultPathTranslator implements PathTranslator {
@Override
public String alignToBaseDirectory(String path, Path basedir) {
String result = path;
if (path != null && basedir != null) {
path = path.replace('\\', File.separatorChar).replace('/', File.separatorChar);
File file = new File(path);
if (file.isAbsolute()) {
// path was already absolute, just normalize file separator and we're done
result = file.getPath();
} else if (file.getPath().startsWith(File.separator)) {
// drive-relative Windows path, don't align with project directory but with drive root
result = file.getAbsolutePath();
} else {
// an ordinary relative path, align with project directory
result = basedir.resolve(path).normalize().toString();
}
}
return result;
}
}

View File

@ -0,0 +1,128 @@
/*
* 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.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.model.Build;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Plugin;
import org.apache.maven.api.model.PluginContainer;
import org.apache.maven.api.model.PluginExecution;
import org.apache.maven.api.model.PluginManagement;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.PluginManagementInjector;
/**
* Handles injection of plugin management into the model.
*
*/
@SuppressWarnings({"checkstyle:methodname"})
@Named
@Singleton
public class DefaultPluginManagementInjector implements PluginManagementInjector {
private ManagementModelMerger merger = new ManagementModelMerger();
@Override
public Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
return merger.mergeManagedBuildPlugins(model);
}
/**
* ManagementModelMerger
*/
protected static class ManagementModelMerger extends MavenModelMerger {
public Model mergeManagedBuildPlugins(Model model) {
Build build = model.getBuild();
if (build != null) {
PluginManagement pluginManagement = build.getPluginManagement();
if (pluginManagement != null) {
return model.withBuild(mergePluginContainerPlugins(build, pluginManagement));
}
}
return model;
}
private Build mergePluginContainerPlugins(Build target, PluginContainer source) {
List<Plugin> src = source.getPlugins();
if (!src.isEmpty()) {
Map<Object, Plugin> managedPlugins = new LinkedHashMap<>(src.size() * 2);
Map<Object, Object> context = Collections.emptyMap();
for (Plugin element : src) {
Object key = getPluginKey().apply(element);
managedPlugins.put(key, element);
}
List<Plugin> newPlugins = new ArrayList<>();
for (Plugin element : target.getPlugins()) {
Object key = getPluginKey().apply(element);
Plugin managedPlugin = managedPlugins.get(key);
if (managedPlugin != null) {
element = mergePlugin(element, managedPlugin, false, context);
}
newPlugins.add(element);
}
return target.withPlugins(newPlugins);
}
return target;
}
@Override
protected void mergePlugin_Executions(
Plugin.Builder builder,
Plugin target,
Plugin source,
boolean sourceDominant,
Map<Object, Object> context) {
List<PluginExecution> src = source.getExecutions();
if (!src.isEmpty()) {
List<PluginExecution> tgt = target.getExecutions();
Map<Object, PluginExecution> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
for (PluginExecution element : src) {
Object key = getPluginExecutionKey().apply(element);
merged.put(key, element);
}
for (PluginExecution element : tgt) {
Object key = getPluginExecutionKey().apply(element);
PluginExecution existing = merged.get(key);
if (existing != null) {
element = mergePluginExecution(element, existing, sourceDominant, context);
}
merged.put(key, element);
}
builder.executions(merged.values());
}
}
}
}

View File

@ -0,0 +1,190 @@
/*
* 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.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import org.apache.maven.api.services.model.*;
/**
* Describes the environmental context used to determine the activation status of profiles.
*
*/
public class DefaultProfileActivationContext implements ProfileActivationContext {
private List<String> activeProfileIds = Collections.emptyList();
private List<String> inactiveProfileIds = Collections.emptyList();
private Map<String, String> systemProperties = Collections.emptyMap();
private Map<String, String> userProperties = Collections.emptyMap();
private Map<String, String> projectProperties = Collections.emptyMap();
private Path projectDirectory;
@Override
public List<String> getActiveProfileIds() {
return activeProfileIds;
}
/**
* Sets the identifiers of those profiles that should be activated by explicit demand.
*
* @param activeProfileIds The identifiers of those profiles to activate, may be {@code null}.
* @return This context, never {@code null}.
*/
public DefaultProfileActivationContext setActiveProfileIds(List<String> activeProfileIds) {
this.activeProfileIds = unmodifiable(activeProfileIds);
return this;
}
@Override
public List<String> getInactiveProfileIds() {
return inactiveProfileIds;
}
/**
* Sets the identifiers of those profiles that should be deactivated by explicit demand.
*
* @param inactiveProfileIds The identifiers of those profiles to deactivate, may be {@code null}.
* @return This context, never {@code null}.
*/
public DefaultProfileActivationContext setInactiveProfileIds(List<String> inactiveProfileIds) {
this.inactiveProfileIds = unmodifiable(inactiveProfileIds);
return this;
}
@Override
public Map<String, String> getSystemProperties() {
return systemProperties;
}
/**
* Sets the system properties to use for interpolation and profile activation. The system properties are collected
* from the runtime environment like {@link System#getProperties()} and environment variables.
*
* @param systemProperties The system properties, may be {@code null}.
* @return This context, never {@code null}.
*/
@SuppressWarnings("unchecked")
public DefaultProfileActivationContext setSystemProperties(Properties systemProperties) {
return setSystemProperties(toMap(systemProperties));
}
/**
* Sets the system properties to use for interpolation and profile activation. The system properties are collected
* from the runtime environment like {@link System#getProperties()} and environment variables.
*
* @param systemProperties The system properties, may be {@code null}.
* @return This context, never {@code null}.
*/
public DefaultProfileActivationContext setSystemProperties(Map<String, String> systemProperties) {
this.systemProperties = unmodifiable(systemProperties);
return this;
}
@Override
public Map<String, String> getUserProperties() {
return userProperties;
}
/**
* Sets the user properties to use for interpolation and profile activation. The user properties have been
* configured directly by the user on his discretion, e.g. via the {@code -Dkey=value} parameter on the command
* line.
*
* @param userProperties The user properties, may be {@code null}.
* @return This context, never {@code null}.
*/
@SuppressWarnings("unchecked")
public DefaultProfileActivationContext setUserProperties(Properties userProperties) {
return setUserProperties(toMap(userProperties));
}
/**
* Sets the user properties to use for interpolation and profile activation. The user properties have been
* configured directly by the user on his discretion, e.g. via the {@code -Dkey=value} parameter on the command
* line.
*
* @param userProperties The user properties, may be {@code null}.
* @return This context, never {@code null}.
*/
public DefaultProfileActivationContext setUserProperties(Map<String, String> userProperties) {
this.userProperties = unmodifiable(userProperties);
return this;
}
@Override
public Path getProjectDirectory() {
return projectDirectory;
}
/**
* Sets the base directory of the current project.
*
* @param projectDirectory The base directory of the current project, may be {@code null} if profile activation
* happens in the context of metadata retrieval rather than project building.
* @return This context, never {@code null}.
*/
public DefaultProfileActivationContext setProjectDirectory(Path projectDirectory) {
this.projectDirectory = projectDirectory;
return this;
}
@Override
public Map<String, String> getProjectProperties() {
return projectProperties;
}
public DefaultProfileActivationContext setProjectProperties(Properties projectProperties) {
return setProjectProperties(toMap(projectProperties));
}
public DefaultProfileActivationContext setProjectProperties(Map<String, String> projectProperties) {
this.projectProperties = unmodifiable(projectProperties);
return this;
}
private static List<String> unmodifiable(List<String> list) {
return list != null ? Collections.unmodifiableList(list) : Collections.emptyList();
}
private static Map<String, String> unmodifiable(Map<String, String> map) {
return map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap();
}
private static Map<String, String> toMap(Properties properties) {
if (properties != null && !properties.isEmpty()) {
return properties.entrySet().stream()
.collect(Collectors.toMap(e -> String.valueOf(e.getKey()), e -> String.valueOf(e.getValue())));
} else {
return null;
}
}
}

View File

@ -0,0 +1,242 @@
/*
* 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.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.model.Build;
import org.apache.maven.api.model.BuildBase;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.ModelBase;
import org.apache.maven.api.model.Plugin;
import org.apache.maven.api.model.PluginContainer;
import org.apache.maven.api.model.PluginExecution;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.model.ReportPlugin;
import org.apache.maven.api.model.ReportSet;
import org.apache.maven.api.model.Reporting;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.*;
/**
* Handles profile injection into the model.
*
*/
@Named
@Singleton
public class DefaultProfileInjector implements ProfileInjector {
private static final Map<Model, Map<List<Profile>, Model>> CACHE = Collections.synchronizedMap(new WeakHashMap<>());
// In order for the weak hash map to work correctly, we must not hold any reference to
// the model used as the key. So we use a dummy model as a placeholder to indicate that
// we want to store the model used as they key.
private static final Model KEY = Model.newInstance();
private final ProfileModelMerger merger = new ProfileModelMerger();
@Override
public Model injectProfiles(
Model model, List<Profile> profiles, ModelBuilderRequest request, ModelProblemCollector problems) {
Model result = CACHE.computeIfAbsent(model, k -> new ConcurrentHashMap<>())
.computeIfAbsent(profiles, l -> doInjectProfiles(model, profiles));
return result == KEY ? model : result;
}
private Model doInjectProfiles(Model model, List<Profile> profiles) {
Model orgModel = model;
for (Profile profile : profiles) {
if (profile != null) {
Model.Builder builder = Model.newBuilder(model);
merger.mergeModelBase(builder, model, profile);
if (profile.getBuild() != null) {
Build build = model.getBuild() != null ? model.getBuild() : Build.newInstance();
Build.Builder bbuilder = Build.newBuilder(build);
merger.mergeBuildBase(bbuilder, build, profile.getBuild());
builder.build(bbuilder.build());
}
model = builder.build();
}
}
return model == orgModel ? KEY : model;
}
/**
* ProfileModelMerger
*/
protected static class ProfileModelMerger extends MavenModelMerger {
public void mergeModelBase(ModelBase.Builder builder, ModelBase target, ModelBase source) {
mergeModelBase(builder, target, source, true, Collections.emptyMap());
}
public void mergeBuildBase(BuildBase.Builder builder, BuildBase target, BuildBase source) {
mergeBuildBase(builder, target, source, true, Collections.emptyMap());
}
@Override
protected void mergePluginContainer_Plugins(
PluginContainer.Builder builder,
PluginContainer target,
PluginContainer source,
boolean sourceDominant,
Map<Object, Object> context) {
List<Plugin> src = source.getPlugins();
if (!src.isEmpty()) {
List<Plugin> tgt = target.getPlugins();
Map<Object, Plugin> master = new LinkedHashMap<>(tgt.size() * 2);
for (Plugin element : tgt) {
Object key = getPluginKey().apply(element);
master.put(key, element);
}
Map<Object, List<Plugin>> predecessors = new LinkedHashMap<>();
List<Plugin> pending = new ArrayList<>();
for (Plugin element : src) {
Object key = getPluginKey().apply(element);
Plugin existing = master.get(key);
if (existing != null) {
existing = mergePlugin(existing, element, sourceDominant, context);
master.put(key, existing);
if (!pending.isEmpty()) {
predecessors.put(key, pending);
pending = new ArrayList<>();
}
} else {
pending.add(element);
}
}
List<Plugin> result = new ArrayList<>(src.size() + tgt.size());
for (Map.Entry<Object, Plugin> entry : master.entrySet()) {
List<Plugin> pre = predecessors.get(entry.getKey());
if (pre != null) {
result.addAll(pre);
}
result.add(entry.getValue());
}
result.addAll(pending);
builder.plugins(result);
}
}
@Override
protected void mergePlugin_Executions(
Plugin.Builder builder,
Plugin target,
Plugin source,
boolean sourceDominant,
Map<Object, Object> context) {
List<PluginExecution> src = source.getExecutions();
if (!src.isEmpty()) {
List<PluginExecution> tgt = target.getExecutions();
Map<Object, PluginExecution> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
for (PluginExecution element : tgt) {
Object key = getPluginExecutionKey().apply(element);
merged.put(key, element);
}
for (PluginExecution element : src) {
Object key = getPluginExecutionKey().apply(element);
PluginExecution existing = merged.get(key);
if (existing != null) {
element = mergePluginExecution(existing, element, sourceDominant, context);
}
merged.put(key, element);
}
builder.executions(merged.values());
}
}
@Override
protected void mergeReporting_Plugins(
Reporting.Builder builder,
Reporting target,
Reporting source,
boolean sourceDominant,
Map<Object, Object> context) {
List<ReportPlugin> src = source.getPlugins();
if (!src.isEmpty()) {
List<ReportPlugin> tgt = target.getPlugins();
Map<Object, ReportPlugin> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
for (ReportPlugin element : tgt) {
Object key = getReportPluginKey().apply(element);
merged.put(key, element);
}
for (ReportPlugin element : src) {
Object key = getReportPluginKey().apply(element);
ReportPlugin existing = merged.get(key);
if (existing != null) {
element = mergeReportPlugin(existing, element, sourceDominant, context);
}
merged.put(key, element);
}
builder.plugins(merged.values());
}
}
@Override
protected void mergeReportPlugin_ReportSets(
ReportPlugin.Builder builder,
ReportPlugin target,
ReportPlugin source,
boolean sourceDominant,
Map<Object, Object> context) {
List<ReportSet> src = source.getReportSets();
if (!src.isEmpty()) {
List<ReportSet> tgt = target.getReportSets();
Map<Object, ReportSet> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
for (ReportSet element : tgt) {
Object key = getReportSetKey().apply(element);
merged.put(key, element);
}
for (ReportSet element : src) {
Object key = getReportSetKey().apply(element);
ReportSet existing = merged.get(key);
if (existing != null) {
element = mergeReportSet(existing, element, sourceDominant, context);
}
merged.put(key, element);
}
builder.reportSets(merged.values());
}
}
}
}

View File

@ -0,0 +1,123 @@
/*
* 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.HashSet;
import java.util.List;
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.Profile;
import org.apache.maven.api.services.BuilderProblem.Severity;
import org.apache.maven.api.services.ModelProblem.Version;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.*;
/**
* Calculates the active profiles among a given collection of profiles.
*
*/
@Named
@Singleton
public class DefaultProfileSelector implements ProfileSelector {
private final List<ProfileActivator> activators;
public DefaultProfileSelector() {
this.activators = new ArrayList<>();
}
@Inject
public DefaultProfileSelector(List<ProfileActivator> activators) {
this.activators = new ArrayList<>(activators);
}
public DefaultProfileSelector addProfileActivator(ProfileActivator profileActivator) {
if (profileActivator != null) {
activators.add(profileActivator);
}
return this;
}
@Override
public List<Profile> getActiveProfiles(
Collection<Profile> profiles, ProfileActivationContext context, ModelProblemCollector problems) {
Collection<String> activatedIds = new HashSet<>(context.getActiveProfileIds());
Collection<String> deactivatedIds = new HashSet<>(context.getInactiveProfileIds());
List<Profile> activeProfiles = new ArrayList<>(profiles.size());
List<Profile> activePomProfilesByDefault = new ArrayList<>();
boolean activatedPomProfileNotByDefault = false;
for (Profile profile : profiles) {
if (!deactivatedIds.contains(profile.getId())) {
if (activatedIds.contains(profile.getId()) || isActive(profile, context, problems)) {
activeProfiles.add(profile);
if (Profile.SOURCE_POM.equals(profile.getSource())) {
activatedPomProfileNotByDefault = true;
}
} else if (isActiveByDefault(profile)) {
if (Profile.SOURCE_POM.equals(profile.getSource())) {
activePomProfilesByDefault.add(profile);
} else {
activeProfiles.add(profile);
}
}
}
}
if (!activatedPomProfileNotByDefault) {
activeProfiles.addAll(activePomProfilesByDefault);
}
return activeProfiles;
}
private boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
boolean isActive = false;
for (ProfileActivator activator : activators) {
if (activator.presentInConfig(profile, context, problems)) {
isActive = true;
try {
if (!activator.isActive(profile, context, problems)) {
return false;
}
} catch (RuntimeException e) {
problems.add(
Severity.ERROR,
Version.BASE,
"Failed to determine activation for profile " + profile.getId(),
profile.getLocation(""),
e);
return false;
}
}
}
return isActive;
}
private boolean isActiveByDefault(Profile profile) {
Activation activation = profile.getActivation();
return activation != null && activation.isActiveByDefault();
}
}

View File

@ -0,0 +1,61 @@
/*
* 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 javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import com.ctc.wstx.stax.WstxInputFactory;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.services.model.*;
@Named
public class DefaultRootLocator implements RootLocator {
public boolean isRootDirectory(Path dir) {
if (Files.isDirectory(dir.resolve(".mvn"))) {
return true;
}
// we're too early to use the modelProcessor ...
Path pom = dir.resolve("pom.xml");
try (InputStream is = Files.newInputStream(pom)) {
XMLStreamReader parser = new WstxInputFactory().createXMLStreamReader(is);
if (parser.nextTag() == XMLStreamReader.START_ELEMENT
&& parser.getLocalName().equals("project")) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
if ("root".equals(parser.getAttributeLocalName(i))) {
return Boolean.parseBoolean(parser.getAttributeValue(i));
}
}
}
} catch (IOException | XMLStreamException e) {
// The root locator can be used very early during the setup of Maven,
// even before the arguments from the command line are parsed. Any exception
// that would happen here should cause the build to fail at a later stage
// (when actually parsing the POM) and will lead to a better exception being
// displayed to the user, so just bail out and return false.
}
return false;
}
}

View File

@ -0,0 +1,216 @@
/*
* 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.Iterator;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.maven.api.model.Build;
import org.apache.maven.api.model.BuildBase;
import org.apache.maven.api.model.CiManagement;
import org.apache.maven.api.model.Dependency;
import org.apache.maven.api.model.DependencyManagement;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.ModelBase;
import org.apache.maven.api.model.Plugin;
import org.apache.maven.api.model.PluginContainer;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.model.ReportPlugin;
import org.apache.maven.api.model.Reporting;
import org.apache.maven.model.v4.MavenMerger;
/**
* As long as Maven controls the BuildPomXMLFilter, the entities that need merging are known.
* All others can simply be copied from source to target to restore the locationTracker
*
* @since 4.0.0
*/
class FileToRawModelMerger extends MavenMerger {
@Override
protected void mergeBuild_Extensions(
Build.Builder builder, Build target, Build source, boolean sourceDominant, Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeBuildBase_Resources(
BuildBase.Builder builder,
BuildBase target,
BuildBase source,
boolean sourceDominant,
Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeBuildBase_TestResources(
BuildBase.Builder builder,
BuildBase target,
BuildBase source,
boolean sourceDominant,
Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeCiManagement_Notifiers(
CiManagement.Builder builder,
CiManagement target,
CiManagement source,
boolean sourceDominant,
Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeDependencyManagement_Dependencies(
DependencyManagement.Builder builder,
DependencyManagement target,
DependencyManagement source,
boolean sourceDominant,
Map<Object, Object> context) {
Iterator<Dependency> sourceIterator = source.getDependencies().iterator();
builder.dependencies(target.getDependencies().stream()
.map(d -> mergeDependency(d, sourceIterator.next(), sourceDominant, context))
.collect(Collectors.toList()));
}
@Override
protected void mergeDependency_Exclusions(
Dependency.Builder builder,
Dependency target,
Dependency source,
boolean sourceDominant,
Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeModel_Contributors(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeModel_Developers(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeModel_Licenses(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeModel_MailingLists(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeModel_Profiles(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
Iterator<Profile> sourceIterator = source.getProfiles().iterator();
builder.profiles(target.getProfiles().stream()
.map(d -> mergeProfile(d, sourceIterator.next(), sourceDominant, context))
.collect(Collectors.toList()));
}
@Override
protected void mergeModelBase_Dependencies(
ModelBase.Builder builder,
ModelBase target,
ModelBase source,
boolean sourceDominant,
Map<Object, Object> context) {
Iterator<Dependency> sourceIterator = source.getDependencies().iterator();
builder.dependencies(target.getDependencies().stream()
.map(d -> mergeDependency(d, sourceIterator.next(), sourceDominant, context))
.collect(Collectors.toList()));
}
@Override
protected void mergeModelBase_PluginRepositories(
ModelBase.Builder builder,
ModelBase target,
ModelBase source,
boolean sourceDominant,
Map<Object, Object> context) {
builder.pluginRepositories(source.getPluginRepositories());
}
@Override
protected void mergeModelBase_Repositories(
ModelBase.Builder builder,
ModelBase target,
ModelBase source,
boolean sourceDominant,
Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergePlugin_Dependencies(
Plugin.Builder builder, Plugin target, Plugin source, boolean sourceDominant, Map<Object, Object> context) {
Iterator<Dependency> sourceIterator = source.getDependencies().iterator();
builder.dependencies(target.getDependencies().stream()
.map(d -> mergeDependency(d, sourceIterator.next(), sourceDominant, context))
.collect(Collectors.toList()));
}
@Override
protected void mergePlugin_Executions(
Plugin.Builder builder, Plugin target, Plugin source, boolean sourceDominant, Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeReporting_Plugins(
Reporting.Builder builder,
Reporting target,
Reporting source,
boolean sourceDominant,
Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergeReportPlugin_ReportSets(
ReportPlugin.Builder builder,
ReportPlugin target,
ReportPlugin source,
boolean sourceDominant,
Map<Object, Object> context) {
// don't merge
}
@Override
protected void mergePluginContainer_Plugins(
PluginContainer.Builder builder,
PluginContainer target,
PluginContainer source,
boolean sourceDominant,
Map<Object, Object> context) {
// don't merge
}
}

View File

@ -0,0 +1,96 @@
/*
* 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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
class Graph {
final Map<String, Set<String>> graph = new LinkedHashMap<>();
synchronized void addEdge(String from, String to) throws CycleDetectedException {
if (graph.computeIfAbsent(from, l -> new HashSet<>()).add(to)) {
List<String> cycle = visitCycle(graph, Collections.singleton(to), new HashMap<>(), new LinkedList<>());
if (cycle != null) {
// remove edge which introduced cycle
throw new CycleDetectedException(
"Edge between '" + from + "' and '" + to + "' introduces to cycle in the graph", cycle);
}
}
}
private enum DfsState {
VISITING,
VISITED
}
private static List<String> visitCycle(
Map<String, Set<String>> graph,
Collection<String> children,
Map<String, DfsState> stateMap,
LinkedList<String> cycle) {
if (children != null) {
for (String v : children) {
DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
if (state == null) {
cycle.addLast(v);
List<String> ret = visitCycle(graph, graph.get(v), stateMap, cycle);
if (ret != null) {
return ret;
}
cycle.removeLast();
stateMap.put(v, DfsState.VISITED);
} else if (state == DfsState.VISITING) {
// we are already visiting this vertex, this mean we have a cycle
int pos = cycle.lastIndexOf(v);
List<String> ret = cycle.subList(pos, cycle.size());
ret.add(v);
return ret;
}
}
}
return null;
}
public static class CycleDetectedException extends Exception {
private final List<String> cycle;
CycleDetectedException(String message, List<String> cycle) {
super(message);
this.cycle = cycle;
}
public List<String> getCycle() {
return cycle;
}
@Override
public String getMessage() {
return super.getMessage() + " " + String.join(" --> ", cycle);
}
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
/**
* MavenBuildTimestamp
*/
public class MavenBuildTimestamp {
// ISO 8601-compliant timestamp for machine readability
public static final String DEFAULT_BUILD_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
public static final String BUILD_TIMESTAMP_FORMAT_PROPERTY = "maven.build.timestamp.format";
public static final TimeZone DEFAULT_BUILD_TIME_ZONE = TimeZone.getTimeZone("Etc/UTC");
private String formattedTimestamp;
public MavenBuildTimestamp() {
this(Instant.now());
}
public MavenBuildTimestamp(Instant time) {
this(time, DEFAULT_BUILD_TIMESTAMP_FORMAT);
}
public MavenBuildTimestamp(Instant time, Map<String, String> properties) {
this(time, properties != null ? properties.get(BUILD_TIMESTAMP_FORMAT_PROPERTY) : null);
}
/**
*
* @deprecated Use {@link #MavenBuildTimestamp(Instant, Map)} or extract the format and pass it
* to {@link #MavenBuildTimestamp(Instant, String)} instead.
*/
@Deprecated
public MavenBuildTimestamp(Instant time, Properties properties) {
this(time, properties != null ? properties.getProperty(BUILD_TIMESTAMP_FORMAT_PROPERTY) : null);
}
public MavenBuildTimestamp(Instant time, String timestampFormat) {
if (timestampFormat == null) {
timestampFormat = DEFAULT_BUILD_TIMESTAMP_FORMAT;
}
if (time == null) {
time = Instant.now();
}
SimpleDateFormat dateFormat = new SimpleDateFormat(timestampFormat);
dateFormat.setCalendar(new GregorianCalendar());
dateFormat.setTimeZone(DEFAULT_BUILD_TIME_ZONE);
formattedTimestamp = dateFormat.format(new Date(time.toEpochMilli()));
}
public String formattedTimestamp() {
return formattedTimestamp;
}
}

View File

@ -0,0 +1,621 @@
/*
* 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.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.api.model.BuildBase;
import org.apache.maven.api.model.CiManagement;
import org.apache.maven.api.model.Dependency;
import org.apache.maven.api.model.DeploymentRepository;
import org.apache.maven.api.model.DistributionManagement;
import org.apache.maven.api.model.Exclusion;
import org.apache.maven.api.model.Extension;
import org.apache.maven.api.model.InputLocation;
import org.apache.maven.api.model.IssueManagement;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.ModelBase;
import org.apache.maven.api.model.Organization;
import org.apache.maven.api.model.Plugin;
import org.apache.maven.api.model.PluginExecution;
import org.apache.maven.api.model.ReportPlugin;
import org.apache.maven.api.model.ReportSet;
import org.apache.maven.api.model.Repository;
import org.apache.maven.api.model.RepositoryBase;
import org.apache.maven.api.model.Scm;
import org.apache.maven.api.model.Site;
import org.apache.maven.model.v4.MavenMerger;
/**
* The domain-specific model merger for the Maven POM, overriding generic code from parent class when necessary with
* more adapted algorithms.
*
*/
public class MavenModelMerger extends MavenMerger {
/**
* The hint key for the child path adjustment used during inheritance for URL calculations.
*/
public static final String CHILD_PATH_ADJUSTMENT = "child-path-adjustment";
/**
* The context key for the artifact id of the target model.
*/
public static final String ARTIFACT_ID = "artifact-id";
public MavenModelMerger() {
super(false);
}
@Override
public Model merge(Model target, Model source, boolean sourceDominant, Map<?, ?> hints) {
return super.merge(target, source, sourceDominant, hints);
}
@Override
protected Model mergeModel(Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
context.put(ARTIFACT_ID, target.getArtifactId());
return super.mergeModel(target, source, sourceDominant, context);
}
@Override
protected void mergeModel_Name(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getName();
if (src != null) {
if (sourceDominant) {
builder.name(src);
builder.location("name", source.getLocation("name"));
}
}
}
@Override
protected void mergeModel_Url(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getUrl();
if (src != null) {
if (sourceDominant) {
builder.url(src);
builder.location("url", source.getLocation("url"));
} else if (target.getUrl() == null) {
builder.url(extrapolateChildUrl(src, source.isChildProjectUrlInheritAppendPath(), context));
builder.location("url", source.getLocation("url"));
}
}
}
/*
* TODO: Whether the merge continues recursively into an existing node or not could be an option for the generated
* merger
*/
@Override
protected void mergeModel_Organization(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
Organization src = source.getOrganization();
if (src != null) {
Organization tgt = target.getOrganization();
if (tgt == null) {
builder.organization(src);
builder.location("organisation", source.getLocation("organisation"));
}
}
}
@Override
protected void mergeModel_IssueManagement(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
IssueManagement src = source.getIssueManagement();
if (src != null) {
IssueManagement tgt = target.getIssueManagement();
if (tgt == null) {
builder.issueManagement(src);
builder.location("issueManagement", source.getLocation("issueManagement"));
}
}
}
@Override
protected void mergeModel_CiManagement(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
CiManagement src = source.getCiManagement();
if (src != null) {
CiManagement tgt = target.getCiManagement();
if (tgt == null) {
builder.ciManagement(src);
builder.location("ciManagement", source.getLocation("ciManagement"));
}
}
}
@Override
protected void mergeModel_ModelVersion(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// neither inherited nor injected
}
@Override
protected void mergeModel_ArtifactId(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// neither inherited nor injected
}
@Override
protected void mergeModel_Profiles(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// neither inherited nor injected
}
@Override
protected void mergeModel_Prerequisites(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// neither inherited nor injected
}
@Override
protected void mergeModel_Licenses(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
builder.licenses(target.getLicenses().isEmpty() ? source.getLicenses() : target.getLicenses());
}
@Override
protected void mergeModel_Developers(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
builder.developers(target.getDevelopers().isEmpty() ? source.getDevelopers() : target.getDevelopers());
}
@Override
protected void mergeModel_Contributors(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
builder.contributors(target.getContributors().isEmpty() ? source.getContributors() : target.getContributors());
}
@Override
protected void mergeModel_MailingLists(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
if (target.getMailingLists().isEmpty()) {
builder.mailingLists(source.getMailingLists());
}
}
@Override
protected void mergeModelBase_Modules(
ModelBase.Builder builder,
ModelBase target,
ModelBase source,
boolean sourceDominant,
Map<Object, Object> context) {
List<String> src = source.getModules();
if (!src.isEmpty() && sourceDominant) {
List<Integer> indices = new ArrayList<>();
List<String> tgt = target.getModules();
Set<String> excludes = new LinkedHashSet<>(tgt);
List<String> merged = new ArrayList<>(tgt.size() + src.size());
merged.addAll(tgt);
for (int i = 0, n = tgt.size(); i < n; i++) {
indices.add(i);
}
for (int i = 0, n = src.size(); i < n; i++) {
String s = src.get(i);
if (!excludes.contains(s)) {
merged.add(s);
indices.add(~i);
}
}
builder.modules(merged);
builder.location(
"modules",
InputLocation.merge(target.getLocation("modules"), source.getLocation("modules"), indices));
}
}
/*
* TODO: The order of the merged list could be controlled by an attribute in the model association: target-first,
* source-first, dominant-first, recessive-first
*/
@Override
protected void mergeModelBase_Repositories(
ModelBase.Builder builder,
ModelBase target,
ModelBase source,
boolean sourceDominant,
Map<Object, Object> context) {
List<Repository> src = source.getRepositories();
if (!src.isEmpty()) {
List<Repository> tgt = target.getRepositories();
Map<Object, Repository> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
List<Repository> dominant, recessive;
if (sourceDominant) {
dominant = src;
recessive = tgt;
} else {
dominant = tgt;
recessive = src;
}
for (Repository element : dominant) {
Object key = getRepositoryKey().apply(element);
merged.put(key, element);
}
for (Repository element : recessive) {
Object key = getRepositoryKey().apply(element);
if (!merged.containsKey(key)) {
merged.put(key, element);
}
}
builder.repositories(merged.values());
}
}
@Override
protected void mergeModelBase_PluginRepositories(
ModelBase.Builder builder,
ModelBase target,
ModelBase source,
boolean sourceDominant,
Map<Object, Object> context) {
List<Repository> src = source.getPluginRepositories();
if (!src.isEmpty()) {
List<Repository> tgt = target.getPluginRepositories();
Map<Object, Repository> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
List<Repository> dominant, recessive;
if (sourceDominant) {
dominant = src;
recessive = tgt;
} else {
dominant = tgt;
recessive = src;
}
for (Repository element : dominant) {
Object key = getRepositoryKey().apply(element);
merged.put(key, element);
}
for (Repository element : recessive) {
Object key = getRepositoryKey().apply(element);
if (!merged.containsKey(key)) {
merged.put(key, element);
}
}
builder.pluginRepositories(merged.values());
}
}
/*
* TODO: Whether duplicates should be removed looks like an option for the generated merger.
*/
@Override
protected void mergeBuildBase_Filters(
BuildBase.Builder builder,
BuildBase target,
BuildBase source,
boolean sourceDominant,
Map<Object, Object> context) {
List<String> src = source.getFilters();
if (!src.isEmpty()) {
List<String> tgt = target.getFilters();
Set<String> excludes = new LinkedHashSet<>(tgt);
List<String> merged = new ArrayList<>(tgt.size() + src.size());
merged.addAll(tgt);
for (String s : src) {
if (!excludes.contains(s)) {
merged.add(s);
}
}
builder.filters(merged);
}
}
@Override
protected void mergeBuildBase_Resources(
BuildBase.Builder builder,
BuildBase target,
BuildBase source,
boolean sourceDominant,
Map<Object, Object> context) {
if (sourceDominant || target.getResources().isEmpty()) {
super.mergeBuildBase_Resources(builder, target, source, sourceDominant, context);
}
}
@Override
protected void mergeBuildBase_TestResources(
BuildBase.Builder builder,
BuildBase target,
BuildBase source,
boolean sourceDominant,
Map<Object, Object> context) {
if (sourceDominant || target.getTestResources().isEmpty()) {
super.mergeBuildBase_TestResources(builder, target, source, sourceDominant, context);
}
}
@Override
protected void mergeDistributionManagement_Relocation(
DistributionManagement.Builder builder,
DistributionManagement target,
DistributionManagement source,
boolean sourceDominant,
Map<Object, Object> context) {}
@Override
protected void mergeDistributionManagement_Repository(
DistributionManagement.Builder builder,
DistributionManagement target,
DistributionManagement source,
boolean sourceDominant,
Map<Object, Object> context) {
DeploymentRepository src = source.getRepository();
if (src != null) {
DeploymentRepository tgt = target.getRepository();
if (sourceDominant || tgt == null) {
tgt = DeploymentRepository.newInstance(false);
builder.repository(mergeDeploymentRepository(tgt, src, sourceDominant, context));
}
}
}
@Override
protected void mergeDistributionManagement_SnapshotRepository(
DistributionManagement.Builder builder,
DistributionManagement target,
DistributionManagement source,
boolean sourceDominant,
Map<Object, Object> context) {
DeploymentRepository src = source.getSnapshotRepository();
if (src != null) {
DeploymentRepository tgt = target.getSnapshotRepository();
if (sourceDominant || tgt == null) {
tgt = DeploymentRepository.newInstance(false);
builder.snapshotRepository(mergeDeploymentRepository(tgt, src, sourceDominant, context));
}
}
}
@Override
protected void mergeDistributionManagement_Site(
DistributionManagement.Builder builder,
DistributionManagement target,
DistributionManagement source,
boolean sourceDominant,
Map<Object, Object> context) {
Site src = source.getSite();
if (src != null) {
Site tgt = target.getSite();
if (tgt == null) {
tgt = Site.newBuilder(false).build();
}
Site.Builder sbuilder = Site.newBuilder(tgt);
if (sourceDominant || tgt == null || isSiteEmpty(tgt)) {
mergeSite(sbuilder, tgt, src, sourceDominant, context);
}
super.mergeSite_ChildSiteUrlInheritAppendPath(sbuilder, tgt, src, sourceDominant, context);
builder.site(sbuilder.build());
}
}
@Override
protected void mergeSite_ChildSiteUrlInheritAppendPath(
Site.Builder builder, Site target, Site source, boolean sourceDominant, Map<Object, Object> context) {}
protected boolean isSiteEmpty(Site site) {
return (site.getId() == null || site.getId().isEmpty())
&& (site.getName() == null || site.getName().isEmpty())
&& (site.getUrl() == null || site.getUrl().isEmpty());
}
@Override
protected void mergeSite_Url(
Site.Builder builder, Site target, Site source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getUrl();
if (src != null) {
if (sourceDominant) {
builder.url(src);
builder.location("url", source.getLocation("url"));
} else if (target.getUrl() == null) {
builder.url(extrapolateChildUrl(src, source.isChildSiteUrlInheritAppendPath(), context));
builder.location("url", source.getLocation("url"));
}
}
}
@Override
protected void mergeScm_Url(
Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getUrl();
if (src != null) {
if (sourceDominant) {
builder.url(src);
builder.location("url", source.getLocation("url"));
} else if (target.getUrl() == null) {
builder.url(extrapolateChildUrl(src, source.isChildScmUrlInheritAppendPath(), context));
builder.location("url", source.getLocation("url"));
}
}
}
@Override
protected void mergeScm_Connection(
Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getConnection();
if (src != null) {
if (sourceDominant) {
builder.connection(src);
builder.location("connection", source.getLocation("connection"));
} else if (target.getConnection() == null) {
builder.connection(extrapolateChildUrl(src, source.isChildScmConnectionInheritAppendPath(), context));
builder.location("connection", source.getLocation("connection"));
}
}
}
@Override
protected void mergeScm_DeveloperConnection(
Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getDeveloperConnection();
if (src != null) {
if (sourceDominant) {
builder.developerConnection(src);
builder.location("developerConnection", source.getLocation("developerConnection"));
} else if (target.getDeveloperConnection() == null) {
String e = extrapolateChildUrl(src, source.isChildScmDeveloperConnectionInheritAppendPath(), context);
builder.developerConnection(e);
builder.location("developerConnection", source.getLocation("developerConnection"));
}
}
}
@Override
protected void mergePlugin_Executions(
Plugin.Builder builder, Plugin target, Plugin source, boolean sourceDominant, Map<Object, Object> context) {
List<PluginExecution> src = source.getExecutions();
if (!src.isEmpty()) {
List<PluginExecution> tgt = target.getExecutions();
Map<Object, PluginExecution> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
for (PluginExecution element : src) {
if (sourceDominant || (element.getInherited() != null ? element.isInherited() : source.isInherited())) {
Object key = getPluginExecutionKey().apply(element);
merged.put(key, element);
}
}
for (PluginExecution element : tgt) {
Object key = getPluginExecutionKey().apply(element);
PluginExecution existing = merged.get(key);
if (existing != null) {
element = mergePluginExecution(element, existing, sourceDominant, context);
}
merged.put(key, element);
}
builder.executions(merged.values());
}
}
@Override
protected void mergePluginExecution_Goals(
PluginExecution.Builder builder,
PluginExecution target,
PluginExecution source,
boolean sourceDominant,
Map<Object, Object> context) {
List<String> src = source.getGoals();
if (!src.isEmpty()) {
List<String> tgt = target.getGoals();
Set<String> excludes = new LinkedHashSet<>(tgt);
List<String> merged = new ArrayList<>(tgt.size() + src.size());
merged.addAll(tgt);
for (String s : src) {
if (!excludes.contains(s)) {
merged.add(s);
}
}
builder.goals(merged);
}
}
@Override
protected void mergeReportPlugin_ReportSets(
ReportPlugin.Builder builder,
ReportPlugin target,
ReportPlugin source,
boolean sourceDominant,
Map<Object, Object> context) {
List<ReportSet> src = source.getReportSets();
if (!src.isEmpty()) {
List<ReportSet> tgt = target.getReportSets();
Map<Object, ReportSet> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
for (ReportSet rset : src) {
if (sourceDominant || (rset.getInherited() != null ? rset.isInherited() : source.isInherited())) {
Object key = getReportSetKey().apply(rset);
merged.put(key, rset);
}
}
for (ReportSet element : tgt) {
Object key = getReportSetKey().apply(element);
ReportSet existing = merged.get(key);
if (existing != null) {
mergeReportSet(element, existing, sourceDominant, context);
}
merged.put(key, element);
}
builder.reportSets(merged.values());
}
}
@Override
protected KeyComputer<Dependency> getDependencyKey() {
return Dependency::getManagementKey;
}
@Override
protected KeyComputer<Plugin> getPluginKey() {
return Plugin::getKey;
}
@Override
protected KeyComputer<PluginExecution> getPluginExecutionKey() {
return PluginExecution::getId;
}
@Override
protected KeyComputer<ReportPlugin> getReportPluginKey() {
return ReportPlugin::getKey;
}
@Override
protected KeyComputer<ReportSet> getReportSetKey() {
return ReportSet::getId;
}
@Override
protected KeyComputer<RepositoryBase> getRepositoryBaseKey() {
return RepositoryBase::getId;
}
@Override
protected KeyComputer<Extension> getExtensionKey() {
return e -> e.getGroupId() + ':' + e.getArtifactId();
}
@Override
protected KeyComputer<Exclusion> getExclusionKey() {
return e -> e.getGroupId() + ':' + e.getArtifactId();
}
protected String extrapolateChildUrl(String parentUrl, boolean appendPath, Map<Object, Object> context) {
return parentUrl;
}
}

View File

@ -16,33 +16,29 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.maven.internal.impl; package org.apache.maven.internal.impl.model;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.api.model.Model; import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.SuperPomProvider; import org.apache.maven.api.services.ModelSource;
import org.apache.maven.api.services.SuperPomProviderException;
@Named /**
@Singleton * Holds a model along with some auxiliary information. This internal utility class assists the model builder during POM
public class DefaultSuperPomProvider implements SuperPomProvider { * processing by providing a means to transport information that cannot be (easily) extracted from the model itself.
*/
record ModelData(ModelSource source, Model model) {
private final org.apache.maven.model.superpom.SuperPomProvider provider; /**
* Gets unique identifier of the model
@Inject *
public DefaultSuperPomProvider(org.apache.maven.model.superpom.SuperPomProvider provider) { * @return The effective identifier of the model, never {@code null}.
this.provider = provider; */
public String id() {
// if source is null, it is the super model, which can be accessed via empty string
return source != null ? source.getLocation() : "";
} }
@Override @Override
public Model getSuperPom(String version) { public String toString() {
try { return String.valueOf(model);
return provider.getSuperModel(version).getDelegate();
} catch (IllegalStateException e) {
throw new SuperPomProviderException("Could not retrieve super pom " + version, e);
}
} }
} }

View File

@ -0,0 +1,133 @@
/*
* 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.nio.file.Path;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.ModelProblem;
/**
* Assists in the handling of model problems.
*
*/
public class ModelProblemUtils {
/**
* Creates a user-friendly source hint for the specified model.
*
* @param model The model to create a source hint for, may be {@code null}.
* @return The user-friendly source hint, never {@code null}.
*/
static String toSourceHint(Model model) {
if (model == null) {
return "";
}
StringBuilder buffer = new StringBuilder(128);
buffer.append(toId(model));
Path pomPath = model.getPomFile();
if (pomPath != null) {
buffer.append(" (").append(pomPath).append(')');
}
return buffer.toString();
}
static String toPath(Model model) {
String path = "";
if (model != null) {
Path pomPath = model.getPomFile();
if (pomPath != null) {
path = pomPath.toAbsolutePath().toString();
}
}
return path;
}
static String toId(Model model) {
if (model == null) {
return "";
}
String groupId = model.getGroupId();
if (groupId == null && model.getParent() != null) {
groupId = model.getParent().getGroupId();
}
String artifactId = model.getArtifactId();
String version = model.getVersion();
if (version == null && model.getParent() != null) {
version = model.getParent().getVersion();
}
return toId(groupId, artifactId, version);
}
/**
* Creates a user-friendly artifact id from the specified coordinates.
*
* @param groupId The group id, may be {@code null}.
* @param artifactId The artifact id, may be {@code null}.
* @param version The version, may be {@code null}.
* @return The user-friendly artifact id, never {@code null}.
*/
static String toId(String groupId, String artifactId, String version) {
return ((groupId != null && !groupId.isEmpty()) ? groupId : "[unknown-group-id]")
+ ':'
+ ((artifactId != null && !artifactId.isEmpty()) ? artifactId : "[unknown-artifact-id]")
+ ':'
+ ((version != null && !version.isEmpty()) ? version : "[unknown-version]");
}
/**
* Creates a string with all location details for the specified model problem. If the project identifier is
* provided, the generated location will omit the model id and source information and only give line/column
* information for problems originating directly from this POM.
*
* @param problem The problem whose location should be formatted, must not be {@code null}.
* @param projectId The {@code <groupId>:<artifactId>:<version>} of the corresponding project, may be {@code null}
* to force output of model id and source.
* @return The formatted problem location or an empty string if unknown, never {@code null}.
*/
public static String formatLocation(ModelProblem problem, String projectId) {
StringBuilder buffer = new StringBuilder(256);
if (!problem.getModelId().equals(projectId)) {
buffer.append(problem.getModelId());
if (!problem.getSource().isEmpty()) {
if (!buffer.isEmpty()) {
buffer.append(", ");
}
buffer.append(problem.getSource());
}
}
if (problem.getLineNumber() > 0) {
if (!buffer.isEmpty()) {
buffer.append(", ");
}
buffer.append("line ").append(problem.getLineNumber());
}
if (problem.getColumnNumber() > 0) {
if (!buffer.isEmpty()) {
buffer.append(", ");
}
buffer.append("column ").append(problem.getColumnNumber());
}
return buffer.toString();
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.nio.file.Path;
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.ActivationFile;
import org.apache.maven.api.services.model.PathTranslator;
import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.RootLocator;
import org.codehaus.plexus.interpolation.AbstractValueSource;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.MapBasedValueSource;
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
/**
* Finds an absolute path for {@link ActivationFile#getExists()} or {@link ActivationFile#getMissing()}
*
*/
@Named
@Singleton
public class ProfileActivationFilePathInterpolator {
private final PathTranslator pathTranslator;
private final RootLocator rootLocator;
@Inject
public ProfileActivationFilePathInterpolator(PathTranslator pathTranslator, RootLocator rootLocator) {
this.pathTranslator = pathTranslator;
this.rootLocator = rootLocator;
}
/**
* Interpolates given {@code path}.
*
* @return absolute path or {@code null} if the input was {@code null}
*/
public String interpolate(String path, ProfileActivationContext context) throws InterpolationException {
if (path == null) {
return null;
}
RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
Path basedir = context.getProjectDirectory();
if (basedir != null) {
interpolator.addValueSource(new AbstractValueSource(false) {
@Override
public Object getValue(String expression) {
if ("basedir".equals(expression) || "project.basedir".equals(expression)) {
return basedir.toAbsolutePath().toString();
}
return null;
}
});
} else if (path.contains("${basedir}")) {
return null;
}
interpolator.addValueSource(new AbstractValueSource(false) {
@Override
public Object getValue(String expression) {
if ("rootDirectory".equals(expression)) {
Path root = rootLocator.findMandatoryRoot(basedir);
return root.toFile().getAbsolutePath();
}
return null;
}
});
interpolator.addValueSource(new MapBasedValueSource(context.getProjectProperties()));
interpolator.addValueSource(new MapBasedValueSource(context.getUserProperties()));
interpolator.addValueSource(new MapBasedValueSource(context.getSystemProperties()));
String absolutePath = interpolator.interpolate(path, "");
return pathTranslator.alignToBaseDirectory(absolutePath, basedir);
}
}

View File

@ -0,0 +1,230 @@
/*
* 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

@ -0,0 +1,123 @@
/*
* 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.profile;
import java.io.File;
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.Profile;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ModelProblem;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.ProfileActivator;
import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolator;
import org.codehaus.plexus.interpolation.InterpolationException;
/**
* Determines profile activation based on the existence/absence of some file.
* File name interpolation support is limited to <code>${project.basedir}</code>
* system properties and user properties.
*
* @see ActivationFile
* @see org.apache.maven.internal.impl.model.DefaultModelValidator#validateRawModel
*/
@Named("file")
@Singleton
public class FileProfileActivator implements ProfileActivator {
private final ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator;
@Inject
public FileProfileActivator(ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator) {
this.profileActivationFilePathInterpolator = profileActivationFilePathInterpolator;
}
@Override
public boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
Activation activation = profile.getActivation();
if (activation == null) {
return false;
}
ActivationFile file = activation.getFile();
if (file == null) {
return false;
}
String path;
boolean missing;
if (file.getExists() != null && !file.getExists().isEmpty()) {
path = file.getExists();
missing = false;
} else if (file.getMissing() != null && !file.getMissing().isEmpty()) {
path = file.getMissing();
missing = true;
} else {
return false;
}
try {
path = profileActivationFilePathInterpolator.interpolate(path, context);
} catch (InterpolationException e) {
problems.add(
BuilderProblem.Severity.ERROR,
ModelProblem.Version.BASE,
"Failed to interpolate file location " + path + " for profile " + profile.getId() + ": "
+ e.getMessage(),
file.getLocation(missing ? "missing" : "exists"),
e);
return false;
}
if (path == null) {
return false;
}
File f = new File(path);
if (!f.isAbsolute()) {
return false;
}
boolean fileExists = f.exists();
return missing != fileExists;
}
@Override
public boolean presentInConfig(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
Activation activation = profile.getActivation();
if (activation == null) {
return false;
}
ActivationFile file = activation.getFile();
return file != null;
}
}

View File

@ -0,0 +1,188 @@
/*
* 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.profile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
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.Profile;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ModelProblem;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.ProfileActivator;
/**
* Determines profile activation based on the version of the current Java runtime.
*
* @see Activation#getJdk()
*/
@Named("jdk-version")
@Singleton
public class JdkVersionProfileActivator implements ProfileActivator {
private static final Pattern FILTER_1 = Pattern.compile("[^\\d._-]");
private static final Pattern FILTER_2 = Pattern.compile("[._-]");
private static final Pattern FILTER_3 = Pattern.compile("\\."); // used for split now
@Override
public boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
Activation activation = profile.getActivation();
if (activation == null) {
return false;
}
String jdk = activation.getJdk();
if (jdk == null) {
return false;
}
String version = context.getSystemProperties().get("java.version");
if (version == null || version.isEmpty()) {
problems.add(
BuilderProblem.Severity.ERROR,
ModelProblem.Version.BASE,
"Failed to determine Java version for profile " + profile.getId(),
activation.getLocation("jdk"));
return false;
}
return isJavaVersionCompatible(jdk, version);
}
public static boolean isJavaVersionCompatible(String requiredJdkRange, String currentJavaVersion) {
if (requiredJdkRange.startsWith("!")) {
return !currentJavaVersion.startsWith(requiredJdkRange.substring(1));
} else if (isRange(requiredJdkRange)) {
return isInRange(currentJavaVersion, getRange(requiredJdkRange));
} else {
return currentJavaVersion.startsWith(requiredJdkRange);
}
}
@Override
public boolean presentInConfig(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
Activation activation = profile.getActivation();
if (activation == null) {
return false;
}
String jdk = activation.getJdk();
return jdk != null;
}
private static boolean isInRange(String value, List<RangeValue> range) {
int leftRelation = getRelationOrder(value, range.get(0), true);
if (leftRelation == 0) {
return true;
}
if (leftRelation < 0) {
return false;
}
return getRelationOrder(value, range.get(1), false) <= 0;
}
private static int getRelationOrder(String value, RangeValue rangeValue, boolean isLeft) {
if (rangeValue.value.isEmpty()) {
return isLeft ? 1 : -1;
}
value = FILTER_1.matcher(value).replaceAll("");
List<String> valueTokens = new ArrayList<>(Arrays.asList(FILTER_2.split(value)));
List<String> rangeValueTokens = new ArrayList<>(Arrays.asList(FILTER_3.split(rangeValue.value)));
addZeroTokens(valueTokens, 3);
addZeroTokens(rangeValueTokens, 3);
for (int i = 0; i < 3; i++) {
int x = Integer.parseInt(valueTokens.get(i));
int y = Integer.parseInt(rangeValueTokens.get(i));
if (x < y) {
return -1;
} else if (x > y) {
return 1;
}
}
if (!rangeValue.closed) {
return isLeft ? -1 : 1;
}
return 0;
}
private static void addZeroTokens(List<String> tokens, int max) {
while (tokens.size() < max) {
tokens.add("0");
}
}
private static boolean isRange(String value) {
return value.startsWith("[") || value.startsWith("(");
}
private static List<RangeValue> getRange(String range) {
List<RangeValue> ranges = new ArrayList<>();
for (String token : range.split(",")) {
if (token.startsWith("[")) {
ranges.add(new RangeValue(token.replace("[", ""), true));
} else if (token.startsWith("(")) {
ranges.add(new RangeValue(token.replace("(", ""), false));
} else if (token.endsWith("]")) {
ranges.add(new RangeValue(token.replace("]", ""), true));
} else if (token.endsWith(")")) {
ranges.add(new RangeValue(token.replace(")", ""), false));
} else if (token.isEmpty()) {
ranges.add(new RangeValue("", false));
}
}
if (ranges.size() < 2) {
ranges.add(new RangeValue("99999999", false));
}
return ranges;
}
private static class RangeValue {
private String value;
private boolean closed;
RangeValue(String value, boolean closed) {
this.value = value.trim();
this.closed = closed;
}
@Override
public String toString() {
return value;
}
}
}

View File

@ -0,0 +1,154 @@
/*
* 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.profile;
import java.util.Locale;
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.ActivationOS;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.ProfileActivator;
/**
* Determines profile activation based on the operating system of the current runtime platform.
*
* @see ActivationOS
*/
@Named("os")
@Singleton
public class OperatingSystemProfileActivator implements ProfileActivator {
private static final String REGEX_PREFIX = "regex:";
@Override
public boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
Activation activation = profile.getActivation();
if (activation == null) {
return false;
}
ActivationOS os = activation.getOs();
if (os == null) {
return false;
}
boolean active = ensureAtLeastOneNonNull(os);
String actualOsName = context.getSystemProperties().get("os.name").toLowerCase(Locale.ENGLISH);
String actualOsArch = context.getSystemProperties().get("os.arch").toLowerCase(Locale.ENGLISH);
String actualOsVersion = context.getSystemProperties().get("os.version").toLowerCase(Locale.ENGLISH);
if (active && os.getFamily() != null) {
active = determineFamilyMatch(os.getFamily(), actualOsName);
}
if (active && os.getName() != null) {
active = determineNameMatch(os.getName(), actualOsName);
}
if (active && os.getArch() != null) {
active = determineArchMatch(os.getArch(), actualOsArch);
}
if (active && os.getVersion() != null) {
active = determineVersionMatch(os.getVersion(), actualOsVersion);
}
return active;
}
@Override
public boolean presentInConfig(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
Activation activation = profile.getActivation();
if (activation == null) {
return false;
}
ActivationOS os = activation.getOs();
return os != null;
}
private boolean ensureAtLeastOneNonNull(ActivationOS os) {
return os.getArch() != null || os.getFamily() != null || os.getName() != null || os.getVersion() != null;
}
private boolean determineVersionMatch(String expectedVersion, String actualVersion) {
String test = expectedVersion;
boolean reverse = false;
final boolean result;
if (test.startsWith(REGEX_PREFIX)) {
result = actualVersion.matches(test.substring(REGEX_PREFIX.length()));
} else {
if (test.startsWith("!")) {
reverse = true;
test = test.substring(1);
}
result = actualVersion.equals(test);
}
return reverse != result;
}
private boolean determineArchMatch(String expectedArch, String actualArch) {
String test = expectedArch;
boolean reverse = false;
if (test.startsWith("!")) {
reverse = true;
test = test.substring(1);
}
boolean result = actualArch.equals(test);
return reverse != result;
}
private boolean determineNameMatch(String expectedName, String actualName) {
String test = expectedName;
boolean reverse = false;
if (test.startsWith("!")) {
reverse = true;
test = test.substring(1);
}
boolean result = actualName.equals(test);
return reverse != result;
}
private boolean determineFamilyMatch(String family, String actualName) {
String test = family;
boolean reverse = false;
if (test.startsWith("!")) {
reverse = true;
test = test.substring(1);
}
boolean result = Os.isFamily(test, actualName);
return reverse != result;
}
}

View File

@ -0,0 +1,233 @@
/*
* 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.profile;
import java.util.Locale;
import java.util.stream.Stream;
/**
* OS support
*/
public class Os {
/**
* The OS Name.
*/
public static final String OS_NAME = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
/**
* The OA architecture.
*/
public static final String OS_ARCH = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH);
/**
* The OS version.
*/
public static final String OS_VERSION = System.getProperty("os.version").toLowerCase(Locale.ENGLISH);
/**
* OS Family
*/
public static final String OS_FAMILY;
/**
* Boolean indicating if the running OS is a Windows system.
*/
public static final boolean IS_WINDOWS;
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_WINDOWS = "windows";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_WIN9X = "win9x";
/**
* OS family that can be tested for. {@value}
*/
public static final String FAMILY_NT = "winnt";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_OS2 = "os/2";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_NETWARE = "netware";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_DOS = "dos";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_MAC = "mac";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_TANDEM = "tandem";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_UNIX = "unix";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_OPENVMS = "openvms";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_ZOS = "z/os";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_OS390 = "os/390";
/**
* OS family that can be tested for. {@value}
*/
private static final String FAMILY_OS400 = "os/400";
/**
* OpenJDK is reported to call MacOS X "Darwin"
*
* @see <a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=44889">bugzilla issue</a>
* @see <a href="https://issues.apache.org/jira/browse/HADOOP-3318">HADOOP-3318</a>
*/
private static final String DARWIN = "darwin";
/**
* The path separator.
*/
private static final String PATH_SEP = System.getProperty("path.separator");
static {
// Those two public constants are initialized here, as they need all the private constants
// above to be initialized first, but the code style imposes the public constants to be
// defined above the private ones...
OS_FAMILY = getOsFamily();
IS_WINDOWS = isFamily(FAMILY_WINDOWS);
}
private Os() {}
/**
* Determines if the OS on which Maven is executing matches the
* given OS family.
*
* @param family the family to check for
* @return true if the OS matches
*
*/
public static boolean isFamily(String family) {
return isFamily(family, OS_NAME);
}
/**
* Determines if the OS on which Maven is executing matches the
* given OS family derived from the given OS name
*
* @param family the family to check for
* @param actualOsName the OS name to check against
* @return true if the OS matches
*
*/
public static boolean isFamily(String family, String actualOsName) {
// windows probing logic relies on the word 'windows' in the OS
boolean isWindows = actualOsName.contains(FAMILY_WINDOWS);
boolean is9x = false;
boolean isNT = false;
if (isWindows) {
// there are only four 9x platforms that we look for
is9x = (actualOsName.contains("95")
|| actualOsName.contains("98")
|| actualOsName.contains("me")
// wince isn't really 9x, but crippled enough to
// be a muchness. Maven doesnt run on CE, anyway.
|| actualOsName.contains("ce"));
isNT = !is9x;
}
switch (family) {
case FAMILY_WINDOWS:
return isWindows;
case FAMILY_WIN9X:
return isWindows && is9x;
case FAMILY_NT:
return isWindows && isNT;
case FAMILY_OS2:
return actualOsName.contains(FAMILY_OS2);
case FAMILY_NETWARE:
return actualOsName.contains(FAMILY_NETWARE);
case FAMILY_DOS:
return PATH_SEP.equals(";") && !isFamily(FAMILY_NETWARE, actualOsName) && !isWindows;
case FAMILY_MAC:
return actualOsName.contains(FAMILY_MAC) || actualOsName.contains(DARWIN);
case FAMILY_TANDEM:
return actualOsName.contains("nonstop_kernel");
case FAMILY_UNIX:
return PATH_SEP.equals(":")
&& !isFamily(FAMILY_OPENVMS, actualOsName)
&& (!isFamily(FAMILY_MAC, actualOsName) || actualOsName.endsWith("x"));
case FAMILY_ZOS:
return actualOsName.contains(FAMILY_ZOS) || actualOsName.contains(FAMILY_OS390);
case FAMILY_OS400:
return actualOsName.contains(FAMILY_OS400);
case FAMILY_OPENVMS:
return actualOsName.contains(FAMILY_OPENVMS);
default:
return actualOsName.contains(family.toLowerCase(Locale.US));
}
}
/**
* Helper method to determine the current OS family.
*
* @return name of current OS family.
*/
private static String getOsFamily() {
return Stream.of(
FAMILY_DOS,
FAMILY_MAC,
FAMILY_NETWARE,
FAMILY_NT,
FAMILY_OPENVMS,
FAMILY_OS2,
FAMILY_OS400,
FAMILY_TANDEM,
FAMILY_UNIX,
FAMILY_WIN9X,
FAMILY_WINDOWS,
FAMILY_ZOS)
.filter(Os::isFamily)
.findFirst()
.orElse(null);
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.profile;
import java.util.Objects;
import java.util.Optional;
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.Profile;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.ProfileActivator;
/**
* Determines profile activation based on the project's packaging.
*/
@Named("packaging")
@Singleton
public class PackagingProfileActivator implements ProfileActivator {
@Override
public boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
return getActivationPackaging(profile).map(p -> isPackaging(context, p)).orElse(false);
}
@Override
public boolean presentInConfig(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
return getActivationPackaging(profile).isPresent();
}
private static boolean isPackaging(ProfileActivationContext context, String p) {
String packaging = context.getUserProperties().get(ProfileActivationContext.PROPERTY_NAME_PACKAGING);
return Objects.equals(p, packaging);
}
private static Optional<String> getActivationPackaging(Profile profile) {
return Optional.ofNullable(profile).map(Profile::getActivation).map(Activation::getPackaging);
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.profile;
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.ActivationProperty;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ModelProblem;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.ProfileActivator;
/**
* Determines profile activation based on the existence or value of some execution property.
*
* @see ActivationProperty
*/
@Named("property")
@Singleton
public class PropertyProfileActivator implements ProfileActivator {
@Override
public boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
Activation activation = profile.getActivation();
if (activation == null) {
return false;
}
ActivationProperty property = activation.getProperty();
if (property == null) {
return false;
}
String name = property.getName();
boolean reverseName = false;
if (name != null && name.startsWith("!")) {
reverseName = true;
name = name.substring(1);
}
if (name == null || name.isEmpty()) {
problems.add(
BuilderProblem.Severity.ERROR,
ModelProblem.Version.BASE,
"The property name is required to activate the profile " + profile.getId(),
property.getLocation(""));
return false;
}
String sysValue = context.getUserProperties().get(name);
if (sysValue == null) {
sysValue = context.getSystemProperties().get(name);
}
String propValue = property.getValue();
if (propValue != null && !propValue.isEmpty()) {
boolean reverseValue = false;
if (propValue.startsWith("!")) {
reverseValue = true;
propValue = propValue.substring(1);
}
// we have a value, so it has to match the system value...
return reverseValue != propValue.equals(sysValue);
} else {
return reverseName != (sysValue != null && !sysValue.isEmpty());
}
}
@Override
public boolean presentInConfig(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
Activation activation = profile.getActivation();
if (activation == null) {
return false;
}
ActivationProperty property = activation.getProperty();
return property != null;
}
}

View File

@ -0,0 +1,149 @@
/*
* 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.resolver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.api.Language;
import org.apache.maven.api.model.DependencyManagement;
import org.apache.maven.api.model.DistributionManagement;
import org.apache.maven.api.model.License;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Prerequisites;
import org.apache.maven.api.model.Repository;
import org.apache.maven.api.services.RepositoryFactory;
import org.apache.maven.internal.impl.InternalSession;
import org.apache.maven.internal.impl.resolver.artifact.MavenArtifactProperties;
import org.apache.maven.internal.impl.resolver.type.DefaultType;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.ArtifactProperties;
import org.eclipse.aether.artifact.ArtifactType;
import org.eclipse.aether.artifact.ArtifactTypeRegistry;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.Exclusion;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
/**
* Populates Aether {@link ArtifactDescriptorResult} from Maven project {@link Model}.
* <p>
* <strong>Note:</strong> This class is part of work in progress and can be changed or removed without notice.
* @since 3.2.4
*/
public class ArtifactDescriptorReaderDelegate {
public void populateResult(InternalSession session, ArtifactDescriptorResult result, Model model) {
ArtifactTypeRegistry stereotypes = session.getSession().getArtifactTypeRegistry();
for (Repository r : model.getRepositories()) {
result.addRepository(session.toRepository(
session.getService(RepositoryFactory.class).createRemote(r)));
}
for (org.apache.maven.api.model.Dependency dependency : model.getDependencies()) {
result.addDependency(convert(dependency, stereotypes));
}
DependencyManagement mgmt = model.getDependencyManagement();
if (mgmt != null) {
for (org.apache.maven.api.model.Dependency dependency : mgmt.getDependencies()) {
result.addManagedDependency(convert(dependency, stereotypes));
}
}
Map<String, Object> properties = new LinkedHashMap<>();
Prerequisites prerequisites = model.getPrerequisites();
if (prerequisites != null) {
properties.put("prerequisites.maven", prerequisites.getMaven());
}
List<License> licenses = model.getLicenses();
properties.put("license.count", licenses.size());
for (int i = 0; i < licenses.size(); i++) {
License license = licenses.get(i);
properties.put("license." + i + ".name", license.getName());
properties.put("license." + i + ".url", license.getUrl());
properties.put("license." + i + ".comments", license.getComments());
properties.put("license." + i + ".distribution", license.getDistribution());
}
result.setProperties(properties);
setArtifactProperties(result, model);
}
private Dependency convert(org.apache.maven.api.model.Dependency dependency, ArtifactTypeRegistry stereotypes) {
ArtifactType stereotype = stereotypes.get(dependency.getType());
if (stereotype == null) {
// TODO: this here is fishy
stereotype = new DefaultType(dependency.getType(), Language.NONE, "", null, false);
}
boolean system = dependency.getSystemPath() != null
&& !dependency.getSystemPath().isEmpty();
Map<String, String> props = null;
if (system) {
props = Collections.singletonMap(MavenArtifactProperties.LOCAL_PATH, dependency.getSystemPath());
}
Artifact artifact = new DefaultArtifact(
dependency.getGroupId(),
dependency.getArtifactId(),
dependency.getClassifier(),
null,
dependency.getVersion(),
props,
stereotype);
List<Exclusion> exclusions = new ArrayList<>(dependency.getExclusions().size());
for (org.apache.maven.api.model.Exclusion exclusion : dependency.getExclusions()) {
exclusions.add(convert(exclusion));
}
return new Dependency(
artifact,
dependency.getScope(),
dependency.getOptional() != null ? dependency.isOptional() : null,
exclusions);
}
private Exclusion convert(org.apache.maven.api.model.Exclusion exclusion) {
return new Exclusion(exclusion.getGroupId(), exclusion.getArtifactId(), "*", "*");
}
private void setArtifactProperties(ArtifactDescriptorResult result, Model model) {
String downloadUrl = null;
DistributionManagement distMgmt = model.getDistributionManagement();
if (distMgmt != null) {
downloadUrl = distMgmt.getDownloadUrl();
}
if (downloadUrl != null && !downloadUrl.isEmpty()) {
Artifact artifact = result.getArtifact();
Map<String, String> props = new HashMap<>(artifact.getProperties());
props.put(ArtifactProperties.DOWNLOAD_URL, downloadUrl);
result.setArtifact(artifact.setProperties(props));
}
}
}

Some files were not shown because too many files have changed in this diff Show More