Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,41 +61,76 @@ public interface XmlNode {
@Deprecated(since = "4.0.0", forRemoval = true)
String CHILDREN_COMBINATION_MODE_ATTRIBUTE = XmlService.CHILDREN_COMBINATION_MODE_ATTRIBUTE;

/**
* @deprecated since 4.0.0.
* Use {@link XmlService#CHILDREN_COMBINATION_MERGE} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
String CHILDREN_COMBINATION_MERGE = XmlService.CHILDREN_COMBINATION_MERGE;


/**
* @deprecated since 4.0.0.
* Use {@link XmlService#CHILDREN_COMBINATION_APPEND} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
String CHILDREN_COMBINATION_APPEND = XmlService.CHILDREN_COMBINATION_APPEND;


/**
* This default mode for combining children DOMs during merge means that where element names match, the process will
* try to merge the element data, rather than putting the dominant and recessive elements (which share the same
* element name) as siblings in the resulting DOM.
*
* @deprecated since 4.0.0.
* Use {@link XmlService#DEFAULT_CHILDREN_COMBINATION_MODE} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
String DEFAULT_CHILDREN_COMBINATION_MODE = XmlService.DEFAULT_CHILDREN_COMBINATION_MODE;

/**
* @deprecated since 4.0.0.
* Use {@link XmlService#SELF_COMBINATION_MODE_ATTRIBUTE} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
String SELF_COMBINATION_MODE_ATTRIBUTE = XmlService.SELF_COMBINATION_MODE_ATTRIBUTE;

/**
* @deprecated since 4.0.0.
* Use {@link XmlService#SELF_COMBINATION_OVERRIDE} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
String SELF_COMBINATION_OVERRIDE = XmlService.SELF_COMBINATION_OVERRIDE;

/**
* @deprecated since 4.0.0.
* Use {@link XmlService#SELF_COMBINATION_MERGE} instead.
*/

@Deprecated(since = "4.0.0", forRemoval = true)
String SELF_COMBINATION_MERGE = XmlService.SELF_COMBINATION_MERGE;
/**
* @deprecated since 4.0.0.
* Use {@link XmlService#SELF_COMBINATION_REMOVE} instead.
*/

@Deprecated(since = "4.0.0", forRemoval = true)
String SELF_COMBINATION_REMOVE = XmlService.SELF_COMBINATION_REMOVE;

/**
* In case of complex XML structures, combining can be done based on id.
*
* @deprecated since 4.0.0.
* Use {@link XmlService#ID_COMBINATION_MODE_ATTRIBUTE} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
String ID_COMBINATION_MODE_ATTRIBUTE = XmlService.ID_COMBINATION_MODE_ATTRIBUTE;

/**
* In case of complex XML structures, combining can be done based on keys.
* This is a comma separated list of attribute names.
*
* @deprecated since 4.0.0.
* Use {@link XmlService#KEYS_COMBINATION_MODE_ATTRIBUTE} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
String KEYS_COMBINATION_MODE_ATTRIBUTE = XmlService.KEYS_COMBINATION_MODE_ATTRIBUTE;
Expand All @@ -105,6 +140,9 @@ public interface XmlNode {
* try to merge the element attributes and values, rather than overriding the recessive element completely with the
* dominant one. This means that wherever the dominant element doesn't provide the value or a particular attribute,
* that value or attribute will be set from the recessive DOM node.
*
* @deprecated since 4.0.0.
* Use {@link XmlService#DEFAULT_SELF_COMBINATION_MODE} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
String DEFAULT_SELF_COMBINATION_MODE = XmlService.DEFAULT_SELF_COMBINATION_MODE;
Expand Down Expand Up @@ -186,76 +224,114 @@ public interface XmlNode {
Object inputLocation();

// Deprecated methods that delegate to new ones
/**
* @deprecated since 4.0.0.
* Use {@link #name()} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@Nonnull
default String getName() {
return name();
}

/**
* @deprecated since 4.0.0.
* Use {@link #namespaceUri()} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@Nonnull
default String getNamespaceUri() {
return namespaceUri();
}

/**
* @deprecated since 4.0.0.
* Use {@link #prefix()} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@Nonnull
default String getPrefix() {
return prefix();
}

/**
* @deprecated since 4.0.0.
* Use {@link #value()} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@Nullable
default String getValue() {
return value();
}

/**
* @deprecated since 4.0.0.
* Use {@link #attributes()} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@Nonnull
default Map<String, String> getAttributes() {
return attributes();
}

/**
* @deprecated since 4.0.0.
* Use {@link #attribute(String)} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@Nullable
default String getAttribute(@Nonnull String name) {
return attribute(name);
}

/**
* @deprecated since 4.0.0.
* Use {@link #children()} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@Nonnull
default List<XmlNode> getChildren() {
return children();
}

/**
* @deprecated since 4.0.0.
* Use {@link #child(String)} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@Nullable
default XmlNode getChild(String name) {
return child(name);
}

/**
* @deprecated since 4.0.0.
* Use {@link #inputLocation()} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@Nullable
default Object getInputLocation() {
return inputLocation();
}

/**
* @deprecated use {@link XmlService#merge(XmlNode, XmlNode, Boolean)} instead
* @deprecated since 4.0.0.
* Use {@link XmlService#merge(XmlNode, XmlNode, Boolean)} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
default XmlNode merge(@Nullable XmlNode source) {
return XmlService.merge(this, source);
}

/**
* @deprecated use {@link XmlService#merge(XmlNode, XmlNode, Boolean)} instead
* @deprecated since 4.0.0.
* Use {@link XmlService#merge(XmlNode, XmlNode, Boolean)} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
default XmlNode merge(@Nullable XmlNode source, @Nullable Boolean childMergeOverride) {
return XmlService.merge(this, source, childMergeOverride);
}


/**
* Merge recessive into dominant and return either {@code dominant}
* with merged information or a clone of {@code recessive} if
Expand All @@ -265,7 +341,8 @@ default XmlNode merge(@Nullable XmlNode source, @Nullable Boolean childMergeOver
* @param recessive if {@code null}, nothing will happen
* @return the merged node
*
* @deprecated use {@link XmlService#merge(XmlNode, XmlNode, Boolean)} instead
* @deprecated since 4.0.0.
* Use {@link XmlService#merge(XmlNode, XmlNode, Boolean)} instead.
*/
@Deprecated(since = "4.0.0", forRemoval = true)
@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,55 @@ public void attachArtifact(@Nonnull Project project, @Nonnull ProducedArtifact a
artifact.getExtension(),
null);
}
if (!Objects.equals(project.getGroupId(), artifact.getGroupId())
|| !Objects.equals(project.getArtifactId(), artifact.getArtifactId())
|| !Objects.equals(
project.getVersion(), artifact.getBaseVersion().toString())) {
throw new IllegalArgumentException(
"The produced artifact must have the same groupId/artifactId/version than the project it is attached to. Expecting "
+ project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion()
+ " but received " + artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
+ artifact.getBaseVersion());
// Verify groupId and version, intentionally allow artifactId to differ as Maven project may be
// multi-module with modular sources structure that provide module names used as artifactIds.
String g1 = project.getGroupId();
String a1 = project.getArtifactId();
String v1 = project.getVersion();
String g2 = artifact.getGroupId();
String a2 = artifact.getArtifactId();
String v2 = artifact.getBaseVersion().toString();

// ArtifactId may differ only for multi-module projects, in which case
// it must match the module name from a source root in modular sources.
boolean isMultiModule = false;
boolean validArtifactId = Objects.equals(a1, a2);
for (SourceRoot sr : getSourceRoots(project)) {
Optional<String> moduleName = sr.module();
if (moduleName.isPresent()) {
isMultiModule = true;
if (moduleName.get().equals(a2)) {
validArtifactId = true;
break;
}
}
}
boolean isSameGroupAndVersion = Objects.equals(g1, g2) && Objects.equals(v1, v2);
if (!(isSameGroupAndVersion && validArtifactId)) {
String message;
if (isMultiModule) {
// Multi-module project: artifactId may match any declared module name
message = String.format(
"Cannot attach artifact to project: groupId and version must match the project, "
+ "and artifactId must match either the project or a declared module name.%n"
+ " Project coordinates: %s:%s:%s%n"
+ " Artifact coordinates: %s:%s:%s%n",
g1, a1, v1, g2, a2, v2);
if (isSameGroupAndVersion) {
message += String.format(
" Hint: The artifactId '%s' does not match the project artifactId '%s' "
+ "nor any declared module name in source roots.",
a2, a1);
}
} else {
// Non-modular project: artifactId must match exactly
message = String.format(
"Cannot attach artifact to project: groupId, artifactId and version must match the project.%n"
+ " Project coordinates: %s:%s:%s%n"
+ " Artifact coordinates: %s:%s:%s",
g1, a1, v1, g2, a2, v2);
}
throw new IllegalArgumentException(message);
}
getMavenProject(project)
.addAttachedArtifact(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,43 +20,102 @@

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Supplier;

import org.apache.maven.api.Language;
import org.apache.maven.api.ProducedArtifact;
import org.apache.maven.api.Project;
import org.apache.maven.api.ProjectScope;
import org.apache.maven.api.services.ArtifactManager;
import org.apache.maven.impl.DefaultModelVersionParser;
import org.apache.maven.impl.DefaultSourceRoot;
import org.apache.maven.impl.DefaultVersionParser;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.util.version.GenericVersionScheme;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;

class DefaultProjectManagerTest {

private DefaultProjectManager projectManager;

private Project project;

private ProducedArtifact artifact;

private Path artifactPath;

@Test
void attachArtifact() {
InternalMavenSession session = Mockito.mock(InternalMavenSession.class);
ArtifactManager artifactManager = Mockito.mock(ArtifactManager.class);
MavenProject mavenProject = new MavenProject();
Project project = new DefaultProject(session, mavenProject);
ProducedArtifact artifact = Mockito.mock(ProducedArtifact.class);
Path path = Paths.get("");
project = new DefaultProject(session, mavenProject);
artifact = Mockito.mock(ProducedArtifact.class);
artifactPath = Paths.get("");
DefaultVersionParser versionParser =
new DefaultVersionParser(new DefaultModelVersionParser(new GenericVersionScheme()));
DefaultProjectManager projectManager = new DefaultProjectManager(session, artifactManager);
projectManager = new DefaultProjectManager(session, artifactManager);

mavenProject.setGroupId("myGroup");
mavenProject.setArtifactId("myArtifact");
mavenProject.setVersion("1.0-SNAPSHOT");
when(artifact.getGroupId()).thenReturn("myGroup");
when(artifact.getArtifactId()).thenReturn("myArtifact");
when(artifact.getBaseVersion()).thenReturn(versionParser.parseVersion("1.0-SNAPSHOT"));
projectManager.attachArtifact(project, artifact, path);
projectManager.attachArtifact(project, artifact, artifactPath);

// Verify that an exception is thrown when the artifactId differs
when(artifact.getArtifactId()).thenReturn("anotherArtifact");
assertThrows(IllegalArgumentException.class, () -> projectManager.attachArtifact(project, artifact, path));
assertExceptionMessageContains("myGroup:myArtifact:1.0-SNAPSHOT", "myGroup:anotherArtifact:1.0-SNAPSHOT");

// Add a Java module. It should relax the restriction on artifactId.
projectManager.addSourceRoot(
project,
new DefaultSourceRoot(
ProjectScope.MAIN,
Language.JAVA_FAMILY,
"org.foo.bar",
null,
Path.of("myProject"),
null,
null,
false,
null,
true));

// Verify that we get the same exception when the artifactId does not match the module name
assertExceptionMessageContains("", "anotherArtifact");

// Verify that no exception is thrown when the artifactId is the module name
when(artifact.getArtifactId()).thenReturn("org.foo.bar");
projectManager.attachArtifact(project, artifact, artifactPath);

// Verify that an exception is thrown when the groupId differs
when(artifact.getGroupId()).thenReturn("anotherGroup");
assertExceptionMessageContains("myGroup:myArtifact:1.0-SNAPSHOT", "anotherGroup:org.foo.bar:1.0-SNAPSHOT");
}

/**
* Verifies that {@code projectManager.attachArtifact(…)} throws an exception,
* and that the expecption message contains the expected and actual <abbr>GAV</abbr>.
*
* @param expectedGAV the actual <abbr>GAV</abbr> that the exception message should contain
* @param actualGAV the actual <abbr>GAV</abbr> that the exception message should contain
*/
private void assertExceptionMessageContains(String expectedGAV, String actualGAV) {
String cause = assertThrows(
IllegalArgumentException.class,
() -> projectManager.attachArtifact(project, artifact, artifactPath))
.getMessage();
Supplier<String> message = () ->
String.format("The exception message does not contain the expected GAV. Message was:%n%s%n", cause);

assertTrue(cause.contains(expectedGAV), message);
assertTrue(cause.contains(actualGAV), message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public DefaultSourceRoot(
@Nonnull Language language,
@Nullable String moduleName,
@Nullable Version targetVersionOrNull,
@Nullable Path directory,
@Nonnull Path directory,
@Nullable List<String> includes,
@Nullable List<String> excludes,
boolean stringFiltering,
Expand Down