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 @@ -24,8 +24,6 @@
import org.openrewrite.internal.ObjectMappers;
import org.openrewrite.internal.PropertyPlaceholderHelper;
import org.openrewrite.internal.RecipeLoader;
import org.openrewrite.marketplace.RecipeListing;
import org.openrewrite.marketplace.RecipeMarketplace;
import org.openrewrite.style.NamedStyles;
import org.openrewrite.style.Style;
import org.yaml.snakeyaml.LoaderOptions;
Expand Down Expand Up @@ -89,34 +87,6 @@ private enum ResourceType {
}
}

public YamlResourceLoader(InputStream yamlInput, URI source, Properties properties, RecipeMarketplace marketplace) {
this.source = source;
this.dependencyResourceLoaders = emptyList();
this.mapper = ObjectMappers.propertyBasedMapper(getClass().getClassLoader());
this.recipeLoader = (recipeName, options) -> {
RecipeListing listing = marketplace.findRecipe(recipeName);
if (listing == null) {
throw new IllegalStateException("Unable to find recipe " + recipeName + " listed in " + source);
}
return listing.prepare(options == null ? emptyMap() : options);
};

maybeAddKotlinModule(mapper);

try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[8192];
while ((nRead = yamlInput.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
this.yamlSource = propertyPlaceholderHelper.replacePlaceholders(
new String(buffer.toByteArray(), StandardCharsets.UTF_8), properties);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

/**
* Load a declarative recipe using the runtime classloader
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@

import java.util.Map;

public interface RecipeBundleReader {
public interface RecipeBundleReader extends AutoCloseable {
RecipeBundle getBundle();

RecipeMarketplace read();

RecipeDescriptor describe(RecipeListing listing);

Recipe prepare(RecipeListing listing, Map<String, Object> options);

default void close() throws Exception {
// no-op
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
*/
package org.openrewrite.marketplace;

public interface RecipeBundleResolver {
public interface RecipeBundleResolver extends AutoCloseable {
String getEcosystem();

RecipeBundleReader resolve(RecipeBundle bundle);

default void close() throws Exception {
// no-op
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,23 +66,23 @@ public class RecipeListing implements Comparable<RecipeListing> {
@With(AccessLevel.PACKAGE)
private final RecipeBundle bundle;

public RecipeBundleReader resolve() {
if (marketplace != null) {
for (RecipeBundleResolver resolver : marketplace.getResolvers()) {
if (resolver.getEcosystem().equals(bundle.getPackageEcosystem())) {
return resolver.resolve(bundle);
}
private RecipeBundleReader resolve(Collection<RecipeBundleResolver> resolvers) {
for (RecipeBundleResolver resolver : resolvers) {
if (resolver.getEcosystem().equals(bundle.getPackageEcosystem())) {
return resolver.resolve(bundle);
}
}
throw new IllegalStateException("This listing has not been configured with a resolver.");
throw new IllegalStateException(String.format("No available resolver for '%s' ecosystem", bundle.getPackageEcosystem()));
}

public RecipeDescriptor describe() {
return resolve().describe(this);
public RecipeDescriptor describe(Collection<RecipeBundleResolver> resolvers) {
// noinspection resource
return resolve(resolvers).describe(this);
}

public Recipe prepare(Map<String, Object> options) {
return resolve().prepare(this, options);
public Recipe prepare(Collection<RecipeBundleResolver> resolvers, Map<String, Object> options) {
// noinspection resource
return resolve(resolvers).prepare(this, options);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@ public class RecipeMarketplace {
"When displaying the category hierarchy of a marketplace, " +
"this is typically not shown.");

private final @Getter List<RecipeBundleResolver> resolvers = new ArrayList<>();

public RecipeMarketplace setResolvers(Collection<RecipeBundleResolver> resolvers) {
this.resolvers.clear();
this.resolvers.addAll(resolvers);
return this;
}

public @Nullable RecipeListing findRecipe(String name) {
return root.findRecipe(name);
}
Expand Down
17 changes: 15 additions & 2 deletions rewrite-core/src/main/java/org/openrewrite/rpc/RewriteRpc.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.openrewrite.config.OptionDescriptor;
import org.openrewrite.internal.RecipeLoader;
import org.openrewrite.marketplace.RecipeBundle;
import org.openrewrite.marketplace.RecipeBundleResolver;
import org.openrewrite.marketplace.RecipeListing;
import org.openrewrite.marketplace.RecipeMarketplace;
import org.openrewrite.rpc.internal.PreparedRecipeCache;
Expand Down Expand Up @@ -103,6 +104,18 @@ public class RewriteRpc {
* the host process has available for its use in composite recipes.
*/
public RewriteRpc(JsonRpc jsonRpc, RecipeMarketplace marketplace) {
this(jsonRpc, marketplace, emptyList());
}

/**
* Creates a new RPC interface that can be used to communicate with a remote.
*
* @param marketplace The marketplace of recipes that this peer makes available.
* Even if this peer is the host process, configuring this
* marketplace allows the remote peer to discover what recipes
* the host process has available for its use in composite recipes.
*/
public RewriteRpc(JsonRpc jsonRpc, RecipeMarketplace marketplace, List<RecipeBundleResolver> resolvers) {
this.jsonRpc = jsonRpc;

jsonRpc.rpc("Visit", new Visit.Handler(localObjects, preparedRecipes,
Expand All @@ -114,7 +127,7 @@ public RewriteRpc(JsonRpc jsonRpc, RecipeMarketplace marketplace) {
jsonRpc.rpc("GetMarketplace", new JsonRpcMethod<Void>() {
@Override
protected Object handle(Void noParams) {
return GetMarketplaceResponse.fromMarketplace(marketplace);
return GetMarketplaceResponse.fromMarketplace(marketplace, resolvers);
}
});
jsonRpc.rpc("TraceGetObject", new JsonRpcMethod<TraceGetObject>() {
Expand Down Expand Up @@ -147,7 +160,7 @@ protected Object handle(Void noParams) {
jsonRpc.rpc("PrepareRecipe", new PrepareRecipe.Handler(preparedRecipes, (id, opts) -> {
RecipeListing listing = marketplace.findRecipe(id);
if (listing != null) {
return listing.prepare(opts);
return listing.prepare(resolvers, opts);
}
// Fall back to loading by class name if not found in marketplace
return new RecipeLoader(null).load(id, opts);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.openrewrite.config.CategoryDescriptor;
import org.openrewrite.config.RecipeDescriptor;
import org.openrewrite.marketplace.RecipeBundle;
import org.openrewrite.marketplace.RecipeBundleResolver;
import org.openrewrite.marketplace.RecipeListing;
import org.openrewrite.marketplace.RecipeMarketplace;

Expand All @@ -29,7 +30,6 @@

import static java.util.Collections.emptySet;


public class GetMarketplaceResponse extends ArrayList<GetMarketplaceResponse.Row> {
@Value
public static class Row {
Expand All @@ -47,28 +47,29 @@ public RecipeMarketplace toMarketplace(RecipeBundle bundle) {
return marketplace;
}

public static GetMarketplaceResponse fromMarketplace(RecipeMarketplace marketplace) {
public static GetMarketplaceResponse fromMarketplace(RecipeMarketplace marketplace, List<RecipeBundleResolver> resolvers) {
Map<String, Row> rowByRecipeId = new LinkedHashMap<>();
for (RecipeMarketplace.Category category : marketplace.getCategories()) {
fromCategory(rowByRecipeId, category, new ArrayList<>());
fromCategory(resolvers, rowByRecipeId, category, new ArrayList<>());
}
GetMarketplaceResponse response = new GetMarketplaceResponse();
response.addAll(rowByRecipeId.values());
return response;
}

private static void fromCategory(Map<String, Row> rowByRecipeId,
private static void fromCategory(List<RecipeBundleResolver> resolvers,
Map<String, Row> rowByRecipeId,
RecipeMarketplace.Category category,
List<CategoryDescriptor> parentCategory) {
List<CategoryDescriptor> categoryPath = new ArrayList<>(parentCategory);
categoryPath.add(new CategoryDescriptor(category.getDisplayName(), "",
category.getDescription(), emptySet(), false, 0, false));
for (RecipeListing recipe : category.getRecipes()) {
rowByRecipeId.computeIfAbsent(recipe.getName(), recipeId ->
new Row(recipe.describe(), new ArrayList<>())).categoryPaths.add(categoryPath);
new Row(recipe.describe(resolvers), new ArrayList<>())).categoryPaths.add(categoryPath);
}
for (RecipeMarketplace.Category child : category.getCategories()) {
fromCategory(rowByRecipeId, child, categoryPath);
fromCategory(resolvers, rowByRecipeId, child, categoryPath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,10 @@
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.openrewrite.marketplace.RecipeBundle.runtimeClasspath;
Expand All @@ -64,16 +63,15 @@ void before() throws IOException {
PipedInputStream serverIn = new PipedInputStream(clientOut);
PipedInputStream clientIn = new PipedInputStream(serverOut);

marketplace = env.toMarketplace(runtimeClasspath())
.setResolvers(singletonList(new TestRecipeBundleResolver()));
marketplace = env.toMarketplace(runtimeClasspath());

JsonMessageFormatter clientFormatter = new JsonMessageFormatter(new ParameterNamesModule());
JsonMessageFormatter serverFormatter = new JsonMessageFormatter(new ParameterNamesModule());

client = new RewriteRpc(new JsonRpc(new HeaderDelimitedMessageHandler(clientFormatter, clientIn, clientOut)), marketplace)
.batchSize(1);

server = new RewriteRpc(new JsonRpc(new HeaderDelimitedMessageHandler(serverFormatter, serverIn, serverOut)), marketplace)
server = new RewriteRpc(new JsonRpc(new HeaderDelimitedMessageHandler(serverFormatter, serverIn, serverOut)), marketplace, List.of(new TestRecipeBundleResolver()))
.batchSize(1);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
import org.openrewrite.Recipe;
import org.openrewrite.config.RecipeDescriptor;
import org.openrewrite.javascript.rpc.JavaScriptRewriteRpc;
import org.openrewrite.marketplace.*;
import org.openrewrite.marketplace.RecipeBundle;
import org.openrewrite.marketplace.RecipeBundleReader;
import org.openrewrite.marketplace.RecipeListing;
import org.openrewrite.marketplace.RecipeMarketplace;

import java.nio.file.Path;
import java.util.List;
import java.util.Map;

@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -54,7 +55,7 @@ public class MavenRecipeBundleReader implements RecipeBundleReader {
private transient @Nullable Environment environment;
transient @Nullable Path recipeJar;
transient @Nullable List<Path> classpath;
private transient @Nullable ClassLoader classLoader;
private transient @Nullable RecipeClassLoader classLoader;

@Override
public RecipeMarketplace read() {
Expand Down Expand Up @@ -98,33 +99,35 @@ RecipeMarketplace marketplaceFromClasspathScan() {
String[] ga = bundle.getPackageName().split(":");
RecipeMarketplace marketplace = new RecipeMarketplace();
List<Path> classpath = classpath();
RecipeClassLoader classLoader = new RecipeClassLoader(requireNonNull(recipeJar), classpath);

// First pass: Scan only the recipe jar for recipes and don't list recipes from dependencies
Environment env = Environment.builder().scanJar(
requireNonNull(recipeJar).toAbsolutePath(),
classpath.stream().map(Path::toAbsolutePath).collect(toList()),
classLoader
).build();

// Second pass: Scan all jars in classpath for recipes and categories
// This gives us proper root categories from category YAMLs.
Environment envWithCategories = environment();

// Bundle version may be set in the environment() call above (as the JARs making up
// the classpath are resolved)
GroupArtifactVersion gav = new GroupArtifactVersion(ga[0], ga[1], bundle.getVersion());

for (RecipeDescriptor descriptor : env.listRecipeDescriptors()) {
marketplace.install(
RecipeListing.fromDescriptor(descriptor, new RecipeBundle(
"maven", gav.getGroupId() + ":" + gav.getArtifactId(),
bundle.getRequestedVersion() == null ? gav.getVersion() : bundle.getRequestedVersion(),
gav.getVersion(), null)),
descriptor.inferCategoriesFromName(envWithCategories)
);
try (RecipeClassLoader classLoader = new RecipeClassLoader(requireNonNull(recipeJar), classpath)) {
// First pass: Scan only the recipe jar for recipes and don't list recipes from dependencies
Environment env = Environment.builder().scanJar(
requireNonNull(recipeJar).toAbsolutePath(),
classpath.stream().map(Path::toAbsolutePath).collect(toList()),
classLoader
).build();

// Second pass: Scan all jars in classpath for recipes and categories
// This gives us proper root categories from category YAMLs.
Environment envWithCategories = environment();

// Bundle version may be set in the environment() call above (as the JARs making up
// the classpath are resolved)
GroupArtifactVersion gav = new GroupArtifactVersion(ga[0], ga[1], bundle.getVersion());

for (RecipeDescriptor descriptor : env.listRecipeDescriptors()) {
marketplace.install(
RecipeListing.fromDescriptor(descriptor, new RecipeBundle(
"maven", gav.getGroupId() + ":" + gav.getArtifactId(),
bundle.getRequestedVersion() == null ? gav.getVersion() : bundle.getRequestedVersion(),
gav.getVersion(), null)),
descriptor.inferCategoriesFromName(envWithCategories)
);
}
return marketplace;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return marketplace;
}

@Override
Expand All @@ -138,6 +141,13 @@ public Recipe prepare(RecipeListing listing, @Nullable Map<String, Object> optio
return r.withOptions(options);
}

@Override
public void close() throws IOException {
if (classLoader != null) {
classLoader.close();
}
}

private Environment environment() {
if (environment == null) {
environment = Environment.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,11 @@ private Optional<MavenResolutionResult> resolveDependencies(GroupArtifactVersion
.flatMap(sf -> sf.getMarkers().findFirst(MavenResolutionResult.class))
.filter(mrr -> !mrr.getDependencies().isEmpty());
}

@Override
public void close() throws Exception {
if (reader != null) {
reader.close();
}
}
}
Loading