blob: 84796bc7d0cdda537e906f861e5d709a085f36c5 [file] [log] [blame]
/*
* 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.geronimo.arthur.impl.nativeimage.generator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.geronimo.arthur.impl.nativeimage.ArthurNativeImageConfiguration;
import org.apache.geronimo.arthur.spi.ArthurExtension;
import org.apache.geronimo.arthur.spi.model.ClassReflectionModel;
import org.apache.geronimo.arthur.spi.model.DynamicProxyModel;
import org.apache.geronimo.arthur.spi.model.ResourcesModel;
import java.io.IOException;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Collections.singletonList;
import static java.util.Comparator.comparing;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
@Slf4j
@RequiredArgsConstructor
public class ConfigurationGenerator implements Runnable {
private final List<ArthurExtension> extensions;
private final ArthurNativeImageConfiguration configuration;
private final Path workingDirectory;
private final BiConsumer<Object, Writer> jsonSerializer;
private final Function<Class<? extends Annotation>, Collection<Class<?>>> classFinder;
private final Function<Class<?>, Collection<Class<?>>> implementationFinder;
private final Function<Class<? extends Annotation>, Collection<Method>> methodFinder;
private final Function<Class<? extends Annotation>, Collection<Field>> fieldFinder;
private final Map<String, String> extensionProperties;
public ConfigurationGenerator(final Iterable<ArthurExtension> extensions, final ArthurNativeImageConfiguration configuration,
final Path workingDirectory, final BiConsumer<Object, Writer> jsonSerializer,
final Function<Class<? extends Annotation>, Collection<Class<?>>> classFinder,
final Function<Class<? extends Annotation>, Collection<Field>> fieldFinder,
final Function<Class<? extends Annotation>, Collection<Method>> methodFinder,
final Function<Class<?>, Collection<Class<?>>> implementationFinder,
final Map<String, String> extensionProperties) {
this.extensions = StreamSupport.stream(extensions.spliterator(), false).collect(toList());
this.configuration = configuration;
this.workingDirectory = workingDirectory;
this.jsonSerializer = jsonSerializer;
this.classFinder = classFinder;
this.fieldFinder = fieldFinder;
this.methodFinder = methodFinder;
this.implementationFinder = implementationFinder;
this.extensionProperties = extensionProperties;
}
@Override
public void run() {
// ensure to have a writtable instance (see Context#setProperty(String, String))
final HashMap<String, String> properties = ofNullable(this.extensionProperties).map(HashMap::new).orElseGet(HashMap::new);
properties.put("workingDirectory", workingDirectory.toAbsolutePath().toString());
final DefautContext context = new DefautContext(configuration, classFinder, methodFinder, fieldFinder, implementationFinder, properties);
for (final ArthurExtension extension : extensions) {
log.debug("Executing {}", extension);
context.setModified(false);
extension.execute(context);
if (context.isModified()) { // todo: loop while it modifies the context?
log.info("Extension {} updated build context", extension.getClass().getName());
}
}
try {
updateConfiguration(context);
} catch (final IOException e) {
throw new IllegalStateException(e);
}
}
private void updateConfiguration(final DefautContext context) throws IOException {
if (!context.getReflections().isEmpty()) {
ensureWorkingDirectoryExists();
final Path json = workingDirectory.resolve("reflection.arthur.json");
log.info("Creating reflection model '{}'", json);
try (final Writer writer = Files.newBufferedWriter(
json, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
jsonSerializer.accept(
context.getReflections().stream()
.collect(groupingBy(ClassReflectionModel::getName))
.values().stream()
.map(this::merge)
.sorted(comparing(ClassReflectionModel::getName))
.collect(toList()),
writer);
}
context.addReflectionConfigFile(json.toAbsolutePath().toString());
}
if (!context.getResources().isEmpty() || !context.getBundles().isEmpty()) {
final ResourcesModel resourcesModel = new ResourcesModel();
if (!context.getResources().isEmpty()) {
resourcesModel.setResources(context.getResources());
}
if (!context.getBundles().isEmpty()) {
resourcesModel.setBundles(context.getBundles());
}
ensureWorkingDirectoryExists();
final Path json = workingDirectory.resolve("resources.arthur.json");
log.info("Creating resources model '{}'", json);
try (final Writer writer = Files.newBufferedWriter(
json, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
jsonSerializer.accept(resourcesModel, writer);
}
context.addResourcesConfigFile(json.toAbsolutePath().toString());
}
if (!context.getDynamicProxyModels().isEmpty()) {
final Set<Collection<String>> proxies = context.getDynamicProxyModels().stream()
.map(DynamicProxyModel::getClasses)
.collect(toCollection(LinkedHashSet::new));
ensureWorkingDirectoryExists();
final Path json = workingDirectory.resolve("dynamicproxies.arthur.json");
log.info("Creating dynamic proxy model '{}'", json);
try (final Writer writer = Files.newBufferedWriter(
json, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
jsonSerializer.accept(proxies, writer);
}
context.addDynamicProxiesConfigFile(json.toAbsolutePath().toString());
}
if (!context.getDynamicClasses().isEmpty()) {
final Path dynamicClassesDir = workingDirectory.resolve("dynamic_classes");
context.getDynamicClasses().forEach((name, content) -> {
final Path target = dynamicClassesDir.resolve(name.replace('.', '/') + ".class");
if (!Files.exists(target.getParent())) {
try {
Files.createDirectories(target.getParent());
} catch (final IOException e) {
throw new IllegalStateException(e);
}
}
try {
Files.write(target, content, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
} catch (final IOException e) {
throw new IllegalStateException(e);
}
});
log.info("Dumped {} generated classes in '{}'", context.getDynamicClasses().size(), dynamicClassesDir);
if (configuration.getClasspath() == null) {
configuration.setClasspath(singletonList(dynamicClassesDir.toString()));
} else {
configuration.setClasspath(Stream.concat(
configuration.getClasspath().stream(),
Stream.of(dynamicClassesDir.toString())
).distinct().collect(toList()));
}
}
}
private ClassReflectionModel merge(final List<ClassReflectionModel> classReflectionModels) {
final Iterator<ClassReflectionModel> modelIterator = classReflectionModels.iterator();
final ClassReflectionModel model = modelIterator.next();
while (modelIterator.hasNext()) {
final ClassReflectionModel next = modelIterator.next();
if (next.getAllDeclaredClasses()) {
model.setAllDeclaredClasses(true);
}
if (next.getAllDeclaredFields()) {
model.setAllDeclaredFields(true);
}
if (next.getAllDeclaredConstructors()) {
model.setAllDeclaredConstructors(true);
}
if (next.getAllDeclaredMethods()) {
model.setAllDeclaredMethods(true);
}
if (next.getAllPublicMethods()) {
model.setAllPublicMethods(true);
}
if (next.getAllPublicFields()) {
model.setAllPublicFields(true);
}
if (next.getAllPublicConstructors()) {
model.setAllPublicConstructors(true);
}
if (next.getAllPublicClasses()) {
model.setAllPublicClasses(true);
}
}
return model;
}
private void ensureWorkingDirectoryExists() throws IOException {
if (!Files.exists(workingDirectory)) {
Files.createDirectories(workingDirectory);
}
}
}