org.openrewrite.config.YamlResourceLoader Maven / Gradle / Ivy
Show all versions of rewrite-core Show documentation
/*
* Copyright 2020 the original author or authors.
*
* 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
*
* https://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.openrewrite.config;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.cfg.ConstructorDetector;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import lombok.Getter;
import org.intellij.lang.annotations.Language;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.internal.PropertyPlaceholderHelper;
import org.openrewrite.style.NamedStyles;
import org.openrewrite.style.Style;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static org.openrewrite.RecipeSerializer.maybeAddKotlinModule;
import static org.openrewrite.Tree.randomId;
import static org.openrewrite.Validated.invalid;
public class YamlResourceLoader implements ResourceLoader {
int refCount = 0;
private static final PropertyPlaceholderHelper propertyPlaceholderHelper =
new PropertyPlaceholderHelper("${", "}", ":");
private final URI source;
private final String yamlSource;
private final ObjectMapper mapper;
@Nullable
private final ClassLoader classLoader;
private final Collection extends ResourceLoader> dependencyResourceLoaders;
@Nullable
private Map> contributors;
@Nullable
private Map> recipeNameToExamples;
@Getter
private enum ResourceType {
Recipe("specs.openrewrite.org/v1beta/recipe"),
Style("specs.openrewrite.org/v1beta/style"),
Category("specs.openrewrite.org/v1beta/category"),
Example("specs.openrewrite.org/v1beta/example"),
Attribution("specs.openrewrite.org/v1beta/attribution");
private final String spec;
ResourceType(String spec) {
this.spec = spec;
}
public static @Nullable ResourceType fromSpec(@Nullable String spec) {
return Arrays.stream(values())
.filter(type -> type.getSpec().equals(spec))
.findAny()
.orElse(null);
}
}
/**
* Load a declarative recipe using the runtime classloader
*
* @param yamlInput Declarative recipe yaml input stream
* @param source Declarative recipe source
* @param properties Placeholder properties
* @throws UncheckedIOException On unexpected IOException
*/
public YamlResourceLoader(InputStream yamlInput, URI source, Properties properties) throws UncheckedIOException {
this(yamlInput, source, properties, null);
}
/**
* Load a declarative recipe, optionally using the specified classloader
*
* @param yamlInput Declarative recipe yaml input stream
* @param source Declarative recipe source
* @param properties Placeholder properties
* @param classLoader Optional classloader to use with jackson. If not specified, the runtime classloader will be used.
* @throws UncheckedIOException On unexpected IOException
*/
public YamlResourceLoader(InputStream yamlInput,
URI source,
Properties properties,
@Nullable ClassLoader classLoader) throws UncheckedIOException {
this(yamlInput, source, properties, classLoader, emptyList());
}
/**
* Load a declarative recipe, optionally using the specified classloader and optionally including resource loaders
* for recipes from dependencies.
*
* @param yamlInput Declarative recipe yaml input stream
* @param source Declarative recipe source
* @param properties Placeholder properties
* @param classLoader Optional classloader to use with jackson. If not specified, the runtime classloader will be used.
* @param dependencyResourceLoaders Optional resource loaders for recipes from dependencies
* @throws UncheckedIOException On unexpected IOException
*/
public YamlResourceLoader(InputStream yamlInput,
URI source,
Properties properties,
@Nullable ClassLoader classLoader,
Collection extends ResourceLoader> dependencyResourceLoaders) throws UncheckedIOException {
this(yamlInput, source, properties, classLoader, dependencyResourceLoaders, jsonMapper -> {
});
}
/**
* Load a declarative recipe, optionally using the specified classloader and optionally including resource loaders
* for recipes from dependencies.
*
* @param yamlInput Declarative recipe yaml input stream
* @param source Declarative recipe source
* @param properties Placeholder properties
* @param classLoader Optional classloader to use with jackson. If not specified, the runtime classloader will be used.
* @param dependencyResourceLoaders Optional resource loaders for recipes from dependencies
* @param mapperCustomizer Customizer for the ObjectMapper
* @throws UncheckedIOException On unexpected IOException
*/
public YamlResourceLoader(InputStream yamlInput, URI source, Properties properties,
@Nullable ClassLoader classLoader,
Collection extends ResourceLoader> dependencyResourceLoaders,
Consumer mapperCustomizer) {
this.source = source;
this.dependencyResourceLoaders = dependencyResourceLoaders;
mapper = JsonMapper.builder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
.constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
.build()
.registerModule(new ParameterNamesModule())
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapperCustomizer.accept(mapper);
maybeAddKotlinModule(mapper);
this.classLoader = classLoader;
if (classLoader != null) {
TypeFactory tf = TypeFactory.defaultInstance().withClassLoader(classLoader);
mapper.setTypeFactory(tf);
}
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[1024];
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);
}
}
private Collection