diff --git a/doc-plugin/pom.xml b/doc-plugin/pom.xml index 6d224a4e..eea65a8c 100644 --- a/doc-plugin/pom.xml +++ b/doc-plugin/pom.xml @@ -49,6 +49,14 @@ org.apache.maven.shared maven-shared-utils + + org.wildfly.channel + channel-core + + + org.wildfly.channel + maven-resolver + diff --git a/doc-plugin/src/main/java/org/wildfly/glow/plugin/doc/ChannelConfiguration.java b/doc-plugin/src/main/java/org/wildfly/glow/plugin/doc/ChannelConfiguration.java new file mode 100644 index 00000000..734367dd --- /dev/null +++ b/doc-plugin/src/main/java/org/wildfly/glow/plugin/doc/ChannelConfiguration.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed 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.wildfly.glow.plugin.doc; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.maven.plugin.MojoExecutionException; +import org.eclipse.aether.repository.RemoteRepository; +import org.wildfly.channel.Channel; +import org.wildfly.channel.ChannelManifestCoordinate; +import org.wildfly.channel.Repository; + +/** + * A channel configuration. Contains a {@code manifest} composed of a {@code groupId}, an {@code artifactId} + * an optional {@code version} or a {@code url}. + * + * @author jdenise + */ +public class ChannelConfiguration { + private static final Pattern FILE_MATCHER = Pattern.compile("^(file|http|https)://.*"); + + private ChannelManifestCoordinate manifest; + + /** + * @return the manifest + */ + public ChannelManifestCoordinate getManifest() { + return manifest; + } + + public void set(final String channel) { + // Is this a URL? + if (FILE_MATCHER.matcher(channel).matches()) { + try { + this.manifest = new ChannelManifestCoordinate(new URL(channel)); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Failed to parse URL for " + channel, e); + } + } else { + // Treat as a Maven GAV + final String[] coords = channel.split(":"); + if (coords.length > 2) { + this.manifest = new ChannelManifestCoordinate(coords[0], coords[1], coords[2]); + } else if (coords.length == 2) { + this.manifest = new ChannelManifestCoordinate(coords[0], coords[1]); + } else { + throw new IllegalArgumentException( + "A channel must be a Maven GAV in the format groupId:artifactId:version. The groupId and artifactId are both required."); + } + } + } + + void setManifest(ChannelManifestCoordinate manifest) { + this.manifest = manifest; + } + + private void validate() throws MojoExecutionException { + if (getManifest() == null) { + throw new MojoExecutionException("Invalid Channel. No manifest specified."); + } + ChannelManifestCoordinate coordinates = getManifest(); + if (coordinates.getUrl() == null && coordinates.getGroupId() == null && coordinates.getArtifactId() == null) { + throw new MojoExecutionException( + "Invalid Channel. Manifest must contain a groupId, artifactId and (optional) version or an url."); + } + if (coordinates.getUrl() == null) { + if (coordinates.getGroupId() == null) { + throw new MojoExecutionException("Invalid Channel. Manifest groupId is null."); + } + if (coordinates.getArtifactId() == null) { + throw new MojoExecutionException("Invalid Channel. Manifest artifactId is null."); + } + } else { + if (coordinates.getGroupId() != null) { + throw new MojoExecutionException("Invalid Channel. Manifest groupId is set although an URL is provided."); + } + if (coordinates.getArtifactId() != null) { + throw new MojoExecutionException("Invalid Channel. Manifest artifactId is set although an URL is provided."); + } + if (coordinates.getVersion() != null) { + throw new MojoExecutionException("Invalid Channel. Manifest version is set although an URL is provided."); + } + } + } + + public Channel toChannel(List repositories) throws MojoExecutionException { + validate(); + List repos = new ArrayList<>(); + for (RemoteRepository r : repositories) { + repos.add(new Repository(r.getId(), r.getUrl())); + } + return new Channel(null, null, null, repos, getManifest(), null, null); + } +} diff --git a/doc-plugin/src/main/java/org/wildfly/glow/plugin/doc/ChannelMavenArtifactRepositoryManager.java b/doc-plugin/src/main/java/org/wildfly/glow/plugin/doc/ChannelMavenArtifactRepositoryManager.java new file mode 100644 index 00000000..d36563b4 --- /dev/null +++ b/doc-plugin/src/main/java/org/wildfly/glow/plugin/doc/ChannelMavenArtifactRepositoryManager.java @@ -0,0 +1,205 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed 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.wildfly.glow.plugin.doc; + +import static org.wildfly.channel.maven.VersionResolverFactory.DEFAULT_REPOSITORY_MAPPER; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.regex.Pattern; + +import org.apache.maven.repository.internal.MavenRepositorySystemUtils; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.VersionRangeRequest; +import org.eclipse.aether.resolution.VersionRangeResolutionException; +import org.eclipse.aether.resolution.VersionRangeResult; +import org.jboss.galleon.api.MavenStreamResolver; +import org.jboss.galleon.universe.maven.MavenArtifact; +import org.jboss.galleon.universe.maven.MavenUniverseException; +import org.jboss.galleon.universe.maven.repo.MavenRepoManager; +import org.wildfly.channel.ArtifactTransferException; +import org.wildfly.channel.Channel; +import org.wildfly.channel.ChannelSession; +import org.wildfly.channel.NoStreamFoundException; +import org.wildfly.channel.Repository; +import org.wildfly.channel.UnresolvedMavenArtifactException; +import org.wildfly.channel.VersionResult; +import org.wildfly.channel.maven.VersionResolverFactory; +import org.wildfly.channel.spi.ChannelResolvable; + +public class ChannelMavenArtifactRepositoryManager implements MavenRepoManager, ChannelResolvable, MavenStreamResolver { + + private final ChannelSession channelSession; + private final RepositorySystem system; + private final DefaultRepositorySystemSession session; + private final List repositories; + + public ChannelMavenArtifactRepositoryManager(List channels, + RepositorySystem system, + RepositorySystemSession contextSession, + List repositories) + throws Exception { + session = MavenRepositorySystemUtils.newSession(); + this.repositories = repositories; + session.setLocalRepositoryManager(contextSession.getLocalRepositoryManager()); + Map mapping = new HashMap<>(); + for (RemoteRepository r : repositories) { + mapping.put(r.getId(), r); + } + Function mapper = r -> { + RemoteRepository rep = mapping.get(r.getId()); + if (rep == null) { + rep = DEFAULT_REPOSITORY_MAPPER.apply(r); + } + return rep; + }; + VersionResolverFactory factory = new VersionResolverFactory(system, session, mapper); + channelSession = new ChannelSession(channels, factory); + this.system = system; + } + + public ChannelSession getChannelSession() { + return channelSession; + } + + @Override + public void resolve(MavenArtifact artifact) throws MavenUniverseException { + try { + resolveFromChannels(artifact); + } catch (ArtifactTransferException ex) { + throw new MavenUniverseException(ex.getLocalizedMessage(), ex); + } catch (NoStreamFoundException ex) { + // unable to resolve the artifact through the channel. + // if the version is defined, let's resolve it directly + if (artifact.getVersion() == null || artifact.getVersion().isEmpty()) { + throw new MavenUniverseException(ex.getLocalizedMessage(), ex); + } + try { + org.wildfly.channel.MavenArtifact mavenArtifact = channelSession.resolveDirectMavenArtifact( + artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(), artifact.getClassifier(), + artifact.getVersion()); + artifact.setPath(mavenArtifact.getFile().toPath()); + } catch (UnresolvedMavenArtifactException e) { + // if the artifact can not be resolved directly either, we abort + throw new MavenUniverseException(e.getLocalizedMessage(), e); + } + } + } + + private void resolveFromChannels(MavenArtifact artifact) throws UnresolvedMavenArtifactException { + org.wildfly.channel.MavenArtifact result = channelSession.resolveMavenArtifact(artifact.getGroupId(), + artifact.getArtifactId(), artifact.getExtension(), artifact.getClassifier(), artifact.getVersion()); + artifact.setVersion(result.getVersion()); + artifact.setPath(result.getFile().toPath()); + } + + @Override + public void resolveLatestVersion(MavenArtifact artifact) throws MavenUniverseException { + throw new MavenUniverseException("Channel resolution can't be applied to Galleon universe"); + } + + @Override + public boolean isResolved(MavenArtifact artifact) throws MavenUniverseException { + throw new MavenUniverseException("Channel resolution can't be applied to Galleon universe"); + } + + @Override + public boolean isLatestVersionResolved(MavenArtifact artifact, String lowestQualifier) throws MavenUniverseException { + throw new MavenUniverseException("Channel resolution can't be applied to Galleon universe"); + } + + @Override + public void resolveLatestVersion(MavenArtifact artifact, String lowestQualifier, Pattern includeVersion, + Pattern excludeVersion) throws MavenUniverseException { + resolveLatestVersion(artifact, null, false); + } + + @Override + public void resolveLatestVersion(MavenArtifact artifact, String lowestQualifier, boolean locallyAvailable) + throws MavenUniverseException { + artifact.setVersion(getLatestVersion(artifact)); + resolve(artifact); + } + + @Override + public String getLatestVersion(MavenArtifact artifact) throws MavenUniverseException { + return getLatestVersion(artifact, null, null, null); + } + + @Override + public String getLatestVersion(MavenArtifact artifact, String lowestQualifier) throws MavenUniverseException { + return getLatestVersion(artifact, lowestQualifier, null, null); + } + + @Override + public String getLatestVersion(MavenArtifact artifact, String lowestQualifier, Pattern includeVersion, + Pattern excludeVersion) throws MavenUniverseException { + try { + return channelSession.resolveMavenArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(), + artifact.getClassifier(), null).getVersion(); + } catch (UnresolvedMavenArtifactException e) { + VersionRangeResult res = getVersionRange(new DefaultArtifact(artifact.getGroupId(), + artifact.getArtifactId(), artifact.getExtension(), artifact.getVersionRange())); + return res.getHighestVersion().toString(); + } + } + + @Override + public List getAllVersions(MavenArtifact artifact) throws MavenUniverseException { + throw new MavenUniverseException("Channel resolution can't be applied to Galleon universe"); + } + + @Override + public List getAllVersions(MavenArtifact artifact, Pattern includeVersion, Pattern excludeVersion) + throws MavenUniverseException { + throw new MavenUniverseException("Channel resolution can't be applied to Galleon universe"); + } + + @Override + public void install(MavenArtifact artifact, Path path) throws MavenUniverseException { + throw new MavenUniverseException("Channel resolution can't be applied to Galleon universe"); + } + + @Override + public String getLatestVersion(String groupId, String artifactId, String extension, String classifier, String baseVersion) { + VersionResult res = channelSession.findLatestMavenArtifactVersion(groupId, artifactId, extension, classifier, + baseVersion); + return res.getVersion(); + } + + private VersionRangeResult getVersionRange(Artifact artifact) throws MavenUniverseException { + VersionRangeRequest rangeRequest = new VersionRangeRequest(); + rangeRequest.setArtifact(artifact); + rangeRequest.setRepositories(repositories); + VersionRangeResult rangeResult; + try { + rangeResult = system.resolveVersionRange(session, rangeRequest); + } catch (VersionRangeResolutionException ex) { + throw new MavenUniverseException(ex.getLocalizedMessage(), ex); + } + return rangeResult; + } + +} diff --git a/doc-plugin/src/main/java/org/wildfly/glow/plugin/doc/ScanDocMojo.java b/doc-plugin/src/main/java/org/wildfly/glow/plugin/doc/ScanDocMojo.java index b81fde0e..c5d116ee 100644 --- a/doc-plugin/src/main/java/org/wildfly/glow/plugin/doc/ScanDocMojo.java +++ b/doc-plugin/src/main/java/org/wildfly/glow/plugin/doc/ScanDocMojo.java @@ -37,6 +37,7 @@ import org.wildfly.glow.Layer; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -50,6 +51,7 @@ import org.jboss.galleon.api.config.GalleonProvisioningConfig; import org.jboss.galleon.universe.UniverseResolver; import org.jboss.galleon.universe.maven.repo.MavenRepoManager; +import org.wildfly.channel.Channel; import org.wildfly.glow.AddOn; import org.wildfly.glow.FeaturePacks; import org.wildfly.glow.LayerMapping; @@ -95,6 +97,37 @@ public class ScanDocMojo extends AbstractMojo { @Parameter(required = false) String repoPath; + /** + * A list of channels used for resolving artifacts while provisioning. + *

+ * Defining a channel: + * + *

+     * 
+     *     
+     *         
+     *             org.wildfly.channels
+     *             wildfly-30.0
+     *         
+     *     
+     *     
+     *         
+     *             https://example.example.org/channel/30
+     *         
+     *     
+     * 
+     * 
+ *

+ */ + @Parameter(required = false) + List channels; + + @Parameter(required = false, defaultValue = "true") + boolean preview; + + @Parameter(required = false, defaultValue = "WildFly") + String serverType; + @Override public void execute() throws MojoExecutionException, MojoFailureException { try { @@ -134,8 +167,17 @@ public void execute() throws MojoExecutionException, MojoFailureException { } try { //Typically under target - Path outputFolder = Paths.get(project.getBuild().getDirectory()); - MavenRepoManager artifactResolver = new MavenArtifactRepositoryManager(repoSystem, repoSession, repositories); + MavenRepoManager artifactResolver; + if (channels != null && !channels.isEmpty()) { + getLog().debug("WildFly channel enabled."); + List lst = new ArrayList<>(); + for (ChannelConfiguration conf : channels) { + lst.add(conf.toChannel(repositories)); + } + artifactResolver = new ChannelMavenArtifactRepositoryManager(lst, repoSystem, repoSession, repositories); + } else { + artifactResolver = new MavenArtifactRepositoryManager(repoSystem, repoSession, repositories); + } UniverseResolver universeResolver = UniverseResolver.builder().addArtifactResolver(artifactResolver).build(); GalleonBuilder provider = new GalleonBuilder(); provider.addArtifactResolver(artifactResolver); @@ -144,15 +186,14 @@ public void execute() throws MojoExecutionException, MojoFailureException { getRules(provider, "bare-metal", universeResolver, rules); Map> cloudRules = new TreeMap<>(); getRules(provider,"cloud", universeResolver, cloudRules); - rulesBuilder.append("## Support for WildFly " + FeaturePacks.getLatestVersion() + "\n\n"); - + rulesBuilder.append("## Support for " + serverType + " " + FeaturePacks.getLatestVersion() + "\n\n"); rulesBuilder.append(buildTable(provider,"bare-metal", rules, false)); rulesBuilder.append(buildTable(provider,"cloud", cloudRules, false)); - - rulesBuilder.append("## Support for WildFly Preview " + FeaturePacks.getLatestVersion() + "\n\n"); - - rulesBuilder.append(buildTable(provider, "bare-metal", rules, true)); - rulesBuilder.append(buildTable(provider, "cloud", cloudRules, true)); + if (preview) { + rulesBuilder.append("## Support for WildFly Preview " + FeaturePacks.getLatestVersion() + "\n\n"); + rulesBuilder.append(buildTable(provider, "bare-metal", rules, true)); + rulesBuilder.append(buildTable(provider, "cloud", cloudRules, true)); + } } finally { System.clearProperty(FeaturePacks.URL_PROPERTY); }