adding documentation and container mojos + small fixes
diff --git a/.gitignore b/.gitignore
index 5988948..14aca17 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,11 @@
+# Intellij Idea
.idea
*.iws
*.iml
*.ipr
+
+# Maven
target
+
+# scm
+.site-content
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..2b5e164
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,30 @@
+////
+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.
+////
+= Apache Geronimo Arthur
+
+A thin layer on top of Orace GraalVM to build native binaries from your Java softwares.
+
+More on our website: https://geronimo.apache.org/arthur/.
+
+== Build the project
+
+[source,bash]
+----
+mvn clean install
+----
+
+TIP: for now the project uses lombok, ensure it is installed in your IDE if you need it.
diff --git a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
index bc9c0b3..884f27c 100644
--- a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
+++ b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
@@ -61,7 +61,7 @@
@Override
public void run() {
- final DefautContext context = new DefautContext(classFinder);
+ final DefautContext context = new DefautContext(configuration, classFinder);
for (final ArthurExtension extension : extensions) {
log.debug("Executing {}", extension);
context.setModified(false);
@@ -86,6 +86,7 @@
json, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
jsonSerializer.accept(context.getReflections(), writer);
}
+ context.addReflectionConfigFile(json.toAbsolutePath().toString());
}
if (!context.getResources().isEmpty() || !context.getBundles().isEmpty()) {
final ResourcesModel resourcesModel = new ResourcesModel();
@@ -102,6 +103,7 @@
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()
@@ -115,6 +117,7 @@
json, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
jsonSerializer.accept(proxies, writer);
}
+ context.addDynamicProxiesConfigFile(json.toAbsolutePath().toString());
}
}
diff --git a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
index d2f68d4..a7f45c8 100644
--- a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
+++ b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
@@ -17,11 +17,13 @@
package org.apache.geronimo.arthur.impl.nativeimage.generator;
import java.lang.annotation.Annotation;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.function.Function;
+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;
@@ -32,6 +34,7 @@
@Data
public class DefautContext implements ArthurExtension.Context {
+ private final ArthurNativeImageConfiguration configuration;
private final Function<Class<? extends Annotation>, Collection<Class<?>>> finder;
private final Collection<ClassReflectionModel> reflections = new HashSet<>();
private final Collection<ResourceModel> resources = new HashSet<>();
@@ -70,4 +73,25 @@
modified = true;
}
}
+
+ public void addReflectionConfigFile(final String path) {
+ if (configuration.getReflectionConfigurationFiles() == null) {
+ configuration.setReflectionConfigurationFiles(new ArrayList<>());
+ }
+ configuration.getReflectionConfigurationFiles().add(path);
+ }
+
+ public void addResourcesConfigFile(final String path) {
+ if (configuration.getResourcesConfigurationFiles() == null) {
+ configuration.setResourcesConfigurationFiles(new ArrayList<>());
+ }
+ configuration.getResourcesConfigurationFiles().add(path);
+ }
+
+ public void addDynamicProxiesConfigFile(final String path) {
+ if (configuration.getDynamicProxyConfigurationFiles() == null) {
+ configuration.setDynamicProxyConfigurationFiles(new ArrayList<>());
+ }
+ configuration.getDynamicProxyConfigurationFiles().add(path);
+ }
}
diff --git a/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java b/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java
index bfcdad5..5088436 100644
--- a/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java
+++ b/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java
@@ -34,6 +34,7 @@
import org.apache.geronimo.arthur.api.RegisterField;
import org.apache.geronimo.arthur.api.RegisterMethod;
import org.apache.geronimo.arthur.api.RegisterResource;
+import org.apache.geronimo.arthur.impl.nativeimage.ArthurNativeImageConfiguration;
import org.apache.geronimo.arthur.impl.nativeimage.generator.DefautContext;
import org.apache.geronimo.arthur.spi.model.ClassReflectionModel;
import org.apache.geronimo.arthur.spi.model.ResourceBundleModel;
@@ -47,7 +48,7 @@
void scan() throws Exception {
final ClassesArchive archive = new ClassesArchive(AnnotationExtensionTest.class.getClasses());
final AnnotationFinder finder = new AnnotationFinder(archive);
- final DefautContext context = new DefautContext(finder::findAnnotatedClasses);
+ final DefautContext context = new DefautContext(new ArthurNativeImageConfiguration(), finder::findAnnotatedClasses);
new AnnotationExtension().execute(context);
try (final Jsonb jsonb = JsonbBuilder.create(
new JsonbConfig().withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL))) {
diff --git a/arthur-maven-plugin/pom.xml b/arthur-maven-plugin/pom.xml
index c18e35b..3e54530 100644
--- a/arthur-maven-plugin/pom.xml
+++ b/arthur-maven-plugin/pom.xml
@@ -69,6 +69,17 @@
</dependency>
<dependency>
+ <groupId>com.google.cloud.tools</groupId>
+ <artifactId>jib-core</artifactId>
+ <version>0.12.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>28.0-jre</version>
+ </dependency>
+
+ <dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-finder-shaded</artifactId>
</dependency>
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ArthurMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ArthurMojo.java
new file mode 100644
index 0000000..513d87e
--- /dev/null
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ArthurMojo.java
@@ -0,0 +1,35 @@
+/*
+ * 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.maven.mojo;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+
+public abstract class ArthurMojo extends AbstractMojo {
+ /**
+ * Once built, the binary path is set in maven properties.
+ * This enables to configure the prefix to use.
+ */
+ @Parameter(defaultValue = "arthur.", property = "arthur.propertiesPrefix")
+ protected String propertiesPrefix;
+
+ @Parameter(defaultValue = "${project}", readonly = true)
+ protected MavenProject project;
+
+ @Parameter(defaultValue = "${settings.offline}", readonly = true)
+ protected boolean offline;
+}
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/DockerMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/DockerMojo.java
new file mode 100644
index 0000000..cca81f0
--- /dev/null
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/DockerMojo.java
@@ -0,0 +1,33 @@
+/*
+ * 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.maven.mojo;
+
+import com.google.cloud.tools.jib.api.Containerizer;
+import com.google.cloud.tools.jib.api.DockerDaemonImage;
+import com.google.cloud.tools.jib.api.InvalidImageReferenceException;
+import org.apache.maven.plugins.annotations.Mojo;
+
+/**
+ * Alternate mojo to jib:dockerBuild to avoid to bundle useless files.
+ * Can be replaced by vanilla jib when it will support it, see https://github.com/GoogleContainerTools/jib/issues/1857
+ */
+@Mojo(name = "docker", threadSafe = true)
+public class DockerMojo extends JibMojo {
+ @Override
+ protected Containerizer createContainer() throws InvalidImageReferenceException {
+ return Containerizer.to(DockerDaemonImage.named(this.to));
+ }
+}
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ImageMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ImageMojo.java
new file mode 100644
index 0000000..c1be72b
--- /dev/null
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ImageMojo.java
@@ -0,0 +1,69 @@
+/*
+ * 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.maven.mojo;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.Optional;
+
+import com.google.cloud.tools.jib.api.Containerizer;
+import com.google.cloud.tools.jib.api.ImageReference;
+import com.google.cloud.tools.jib.api.InvalidImageReferenceException;
+import com.google.cloud.tools.jib.api.RegistryImage;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
+import org.apache.maven.settings.crypto.SettingsDecrypter;
+import org.apache.maven.settings.crypto.SettingsDecryptionResult;
+
+/**
+ * Alternate mojo to jib:build to avoid to bundle useless files.
+ * Can be replaced by vanilla jib when it will support it, see https://github.com/GoogleContainerTools/jib/issues/1857
+ */
+@Mojo(name = "image", threadSafe = true)
+public class ImageMojo extends JibMojo {
+ /**
+ * Server identifier (in settings.xml) used to authenticate to the remote image registry.
+ */
+ @Parameter(property = "arthur.serverId")
+ private String serverId;
+
+ @Component
+ private SettingsDecrypter settingsDecrypter;
+
+ @Parameter(defaultValue = "${session}", readonly = true)
+ private MavenSession session;
+
+ @Override
+ protected Containerizer createContainer() throws InvalidImageReferenceException {
+ final ImageReference reference = ImageReference.parse(to);
+ final RegistryImage image = RegistryImage.named(reference);
+ registerCredentials(reference, image);
+ return Containerizer.to(image);
+ }
+
+ private void registerCredentials(final ImageReference reference, final RegistryImage registryImage) {
+ ofNullable(serverId)
+ .map(Optional::of)
+ .orElseGet(() -> ofNullable(reference.getRegistry()))
+ .map(id -> session.getSettings().getServer(id))
+ .map(it -> settingsDecrypter.decrypt(new DefaultSettingsDecryptionRequest(it)))
+ .map(SettingsDecryptionResult::getServer)
+ .ifPresent(server -> registryImage.addCredential(server.getUsername(), server.getPassword()));
+ }
+}
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/JibMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/JibMojo.java
new file mode 100644
index 0000000..27fda59
--- /dev/null
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/JibMojo.java
@@ -0,0 +1,303 @@
+/*
+ * 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.maven.mojo;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.stream.Collectors.toList;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import com.google.cloud.tools.jib.api.AbsoluteUnixPath;
+import com.google.cloud.tools.jib.api.CacheDirectoryCreationException;
+import com.google.cloud.tools.jib.api.Containerizer;
+import com.google.cloud.tools.jib.api.FilePermissions;
+import com.google.cloud.tools.jib.api.ImageReference;
+import com.google.cloud.tools.jib.api.InvalidImageReferenceException;
+import com.google.cloud.tools.jib.api.Jib;
+import com.google.cloud.tools.jib.api.JibContainer;
+import com.google.cloud.tools.jib.api.JibContainerBuilder;
+import com.google.cloud.tools.jib.api.LayerConfiguration;
+import com.google.cloud.tools.jib.api.LayerEntry;
+import com.google.cloud.tools.jib.api.LogEvent;
+import com.google.cloud.tools.jib.api.Ports;
+import com.google.cloud.tools.jib.api.RegistryException;
+import org.apache.maven.plugins.annotations.Parameter;
+
+public abstract class JibMojo extends ArthurMojo {
+ /**
+ * Base image to use. Scratch will ensure it starts from an empty image.
+ */
+ @Parameter(property = "arthur.from", defaultValue = "scratch")
+ private String from;
+
+ /**
+ * Ports to expose.
+ */
+ @Parameter(property = "arthur.ports")
+ private List<String> ports;
+
+ /**
+ * Other files to include in the image, note that their permissions will not be executable.
+ */
+ @Parameter(property = "arthur.files")
+ private List<File> otherFiles;
+
+ /**
+ * Program arguments.
+ */
+ @Parameter(property = "arthur.programArguments")
+ private List<String> programArguments;
+
+ /**
+ * Image environment.
+ */
+ @Parameter(property = "arthur.environment")
+ private Map<String, String> environment;
+
+ /**
+ * Image labels.
+ */
+ @Parameter(property = "arthur.labels")
+ private Map<String, String> labels;
+
+ /**
+ * Timestamp creation for the image, it is recommended to set it fixed for reproducibility.
+ */
+ @Parameter(property = "arthur.creationTimestamp", defaultValue = "1")
+ private long creationTimestamp;
+
+ /**
+ * Entry point to use.
+ */
+ @Parameter(property = "arthur.entrypoint", defaultValue = "/${project.artifactId}")
+ private String entrypoint;
+
+ /**
+ * Where is the binary to include. It defaults on native-image output if done before in the same execution
+ */
+ @Parameter(property = "arthur.binarySource")
+ private File binarySource;
+
+ /**
+ * Should base images be cached.
+ */
+ @Parameter(property = "arthur.enableCache", defaultValue = "true")
+ private boolean enableCache;
+
+ /**
+ * Are insecure registries allowed.
+ */
+ @Parameter(property = "arthur.allowInsecureRegistries", defaultValue = "false")
+ private boolean allowInsecureRegistries;
+
+ /**
+ * Where to cache application layers.
+ */
+ @Parameter(property = "arthur.applicationLayersCache", defaultValue = "${project.build.directory}/arthur_jib_cache/application")
+ private File applicationLayersCache;
+
+ /**
+ * Where to cache base layers layers (if any).
+ */
+ @Parameter(property = "arthur.baseLayersCache", defaultValue = "${project.build.directory}/arthur_jib_cache/base")
+ private File baseLayersCache;
+
+ /**
+ * Number of threads used to build.
+ */
+ @Parameter(property = "arthur.threads", defaultValue = "1")
+ private int threads;
+
+ /**
+ * Build timeout in milliseconds if it is using threads > 1.
+ */
+ @Parameter(property = "arthur.timeout", defaultValue = "3600000")
+ private long timeout;
+
+ /**
+ * Target image name.
+ */
+ @Parameter(property = "arthur.to", defaultValue = "${project.artifactId}:${project.version}")
+ protected String to;
+
+ protected abstract Containerizer createContainer() throws InvalidImageReferenceException;
+
+ @Override
+ public void execute() {
+ final JibContainerBuilder prepared = prepare();
+ withExecutor(es -> {
+ try {
+ final Containerizer containerizer = createContainer();
+ final JibContainer container = prepared.containerize(configure(containerizer, es));
+ if (propertiesPrefix != null) {
+ project.getProperties().setProperty(propertiesPrefix + "image.imageId", container.getImageId().getHash());
+ project.getProperties().setProperty(propertiesPrefix + "image.digest", container.getDigest().getHash());
+ }
+ getLog().info("Built '" + to + "'");
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (final RegistryException | IOException | CacheDirectoryCreationException | ExecutionException | InvalidImageReferenceException e) {
+ throw new IllegalStateException(e);
+ }
+ });
+ }
+
+ private void withExecutor(final Consumer<ExecutorService> consumer) {
+ if (threads > 1) {
+ final ExecutorService executorService = Executors.newFixedThreadPool(threads, new ThreadFactory() {
+ private final AtomicInteger counter = new AtomicInteger();
+
+ @Override
+ public Thread newThread(final Runnable r) {
+ return new Thread(r, JibMojo.class.getName() + "-" + counter.incrementAndGet());
+ }
+ });
+ try {
+ consumer.accept(executorService);
+ } finally {
+ executorService.shutdown();
+ try {
+ executorService.awaitTermination(timeout, MILLISECONDS);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ } else {
+ consumer.accept(null);
+ }
+ }
+
+ private Containerizer configure(final Containerizer to, final ExecutorService executorService) {
+ to.setAlwaysCacheBaseImage(enableCache);
+ to.setAllowInsecureRegistries(allowInsecureRegistries);
+ to.setApplicationLayersCache(applicationLayersCache.toPath());
+ to.setBaseImageLayersCache(baseLayersCache.toPath());
+ to.setOfflineMode(offline);
+ to.setToolName("Arthur " + getClass().getSimpleName().replace("Mojo", ""));
+ to.setExecutorService(executorService);
+ to.addEventHandler(LogEvent.class, event -> {
+ switch (event.getLevel()) {
+ case INFO:
+ case LIFECYCLE:
+ case PROGRESS:
+ getLog().info(event.getMessage());
+ break;
+ case WARN:
+ getLog().warn(event.getMessage());
+ break;
+ case ERROR:
+ getLog().error(event.getMessage());
+ break;
+ case DEBUG:
+ default:
+ getLog().debug(event.getMessage());
+ break;
+ }
+ });
+ return to;
+ }
+
+ private JibContainerBuilder prepare() {
+ try {
+ final JibContainerBuilder from = Jib.from(ImageReference.parse(this.from));
+ if (ports != null) {
+ from.setExposedPorts(Ports.parse(ports));
+ }
+ if (environment != null) {
+ from.setEnvironment(environment);
+ }
+ if (labels != null) {
+ from.setLabels(labels);
+ }
+ if (programArguments != null) {
+ from.setProgramArguments(programArguments);
+ }
+ from.setCreationTime(creationTimestamp < 0 ? Instant.now() : Instant.ofEpochMilli(creationTimestamp));
+ from.setEntrypoint(entrypoint);
+
+ final Path source = ofNullable(binarySource)
+ .map(File::toPath)
+ .orElseGet(() -> Paths.get(requireNonNull(
+ project.getProperties().getProperty(propertiesPrefix + "binary.path"),
+ "No binary path found, ensure to run native-image before or set entrypoint")));
+ from.setLayers(Stream.concat(
+ otherFiles != null && !otherFiles.isEmpty() ?
+ Stream.of(createOthersLayer()) :
+ Stream.empty(),
+ Stream.of(LayerConfiguration.builder()
+ .setName("Binary")
+ .addEntry(new LayerEntry(
+ source, AbsoluteUnixPath.get(entrypoint), FilePermissions.fromOctalString("755"),
+ getTimestamp(source)))
+ .build()))
+ .collect(toList()));
+
+ return from;
+ } catch (final InvalidImageReferenceException | IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private Instant getTimestamp(final Path source) throws IOException {
+ return creationTimestamp < 0 ? Files.getLastModifiedTime(source).toInstant() : Instant.ofEpochMilli(creationTimestamp);
+ }
+
+ private LayerConfiguration createOthersLayer() {
+ final LayerConfiguration.Builder builder = LayerConfiguration.builder().setName("Others");
+ otherFiles.stream().map(File::toPath).forEach(f -> {
+ final AbsoluteUnixPath containerPath = AbsoluteUnixPath.get(project.getBasedir().toPath().relativize(f).toString());
+ if (containerPath.toString().contains("..")) {
+ throw new IllegalArgumentException("You can only include files included in basedir");
+ }
+ try {
+ if (Files.isDirectory(f)) {
+ builder.addEntryRecursive(
+ f, containerPath,
+ (l, c) -> FilePermissions.DEFAULT_FILE_PERMISSIONS,
+ (l, c) -> {
+ try {
+ return getTimestamp(l);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ });
+ } else {
+ builder.addEntry(f, containerPath, FilePermissions.DEFAULT_FILE_PERMISSIONS, getTimestamp(f));
+ }
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ });
+ return builder.build();
+ }
+}
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java
index 4e72b68..c36c2aa 100644
--- a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java
@@ -67,8 +67,11 @@
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
+/**
+ * Generates a native binary from current project.
+ */
@Mojo(name = "native-image", defaultPhase = PACKAGE, requiresDependencyResolution = TEST, threadSafe = true)
-public class NativeImageMojo extends AbstractMojo {
+public class NativeImageMojo extends ArthurMojo {
//
// ArthurNativeImageConfiguration
//
@@ -317,21 +320,25 @@
@Parameter(property = "arthur.supportedTypes", defaultValue = "jar,zip")
private List<String> supportedTypes;
+ /**
+ * Should jar be used instead of exploded folder (target/classes).
+ * Note this option disable the support of module test classes.
+ */
+ @Parameter(property = "project.usePackagedArtifact", defaultValue = "false")
+ private boolean usePackagedArtifact;
+
@Parameter(defaultValue = "${project.packaging}", readonly = true)
private String packaging;
+ @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.${project.packaging}")
+ private File jar;
+
@Parameter(defaultValue = "${project.build.outputDirectory}")
private File classes;
@Parameter(defaultValue = "${project.build.testOutputDirectory}")
private File testClasses;
- @Parameter(defaultValue = "${settings.offline}", readonly = true)
- private boolean offline;
-
- @Parameter(defaultValue = "${project}", readonly = true)
- private MavenProject project;
-
@Parameter(defaultValue = "${repositorySystemSession}")
protected RepositorySystemSession repositorySystemSession;
@@ -406,6 +413,10 @@
} finally {
thread.setContextClassLoader(oldLoader);
}
+
+ if (propertiesPrefix != null) {
+ project.getProperties().setProperty(propertiesPrefix + "binary.path", output);
+ }
}
private String buildCacheGav(final String graalPlatform) {
@@ -433,9 +444,11 @@
private Stream<File> findClasspathFiles() {
return Stream.concat(Stream.concat(
- Stream.concat(
- Stream.of(classes),
- supportTestArtifacts ? Stream.of(testClasses) : Stream.empty()),
+ usePackagedArtifact ?
+ Stream.of(jar) :
+ Stream.concat(
+ Stream.of(classes),
+ supportTestArtifacts ? Stream.of(testClasses) : Stream.empty()),
project.getArtifacts().stream()
.filter(a -> !excludedArtifacts.contains(a.getGroupId() + ':' + a.getArtifactId()))
.filter(this::handleTestInclusion)
diff --git a/documentation/pom.xml b/documentation/pom.xml
new file mode 100644
index 0000000..2fed8f6
--- /dev/null
+++ b/documentation/pom.xml
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>arthur</artifactId>
+ <groupId>org.apache.geronimo.arthur</groupId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>documentation</artifactId>
+ <name>Arthur :: Documentation</name>
+
+ <properties>
+ <geronimo-arthur.shortname>documentation</geronimo-arthur.shortname>
+
+ <doc.mojo.basedir>
+ ${project.parent.basedir}/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo
+ </doc.mojo.basedir>
+ </properties>
+
+ <dependencies>
+ <!-- we want to be last in the reactor to be able to rely on relative paths if we generate doc -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>arthur-maven-plugin</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <!-- generation dependencies -->
+ <dependency>
+ <groupId>org.tomitribe</groupId>
+ <artifactId>tomitribe-crest</artifactId>
+ <version>0.11-SNAPSHOT</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.xbean</groupId>
+ <artifactId>xbean-asm5-shaded</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.xbean</groupId>
+ <artifactId>xbean-finder-shaded</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.asciidoctor</groupId>
+ <artifactId>asciidoctorj</artifactId>
+ <version>2.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.asciidoctor</groupId>
+ <artifactId>asciidoctorj-diagram</artifactId>
+ <version>1.5.18</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jruby</groupId>
+ <artifactId>jruby-complete</artifactId>
+ <version>9.2.8.0</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.6.0</version>
+ <executions>
+ <execution>
+ <id>default-cli</id>
+ <phase />
+ <goals>
+ <goal>java</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>generate</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <mainClass>org.apache.geronimo.arthur.documentation.DocumentationGeneratorLauncher</mainClass>
+ <arguments>
+ <argument>generate</argument>
+ <argument>--work-directory=${project.build.directory}/generated_documentation</argument>
+ <argument>--content-location=${project.basedir}/src/content</argument>
+ <argument>--static-location=${project.basedir}/src/static</argument>
+ <argument>--output=${project.build.directory}/documentation</argument>
+ <argument>--template-header=${project.basedir}/src/template/header.html</argument>
+ <argument>--template-footer=${project.basedir}/src/template/footer.html</argument>
+ <argument>--mojo=${doc.mojo.basedir}/DockerMojo.java</argument>
+ <argument>--mojo=${doc.mojo.basedir}/ImageMojo.java</argument>
+ <argument>--mojo=${doc.mojo.basedir}/NativeImageMojo.java</argument>
+ </arguments>
+ <systemProperties>
+ <systemProperty>
+ <key>org.slf4j.simpleLogger.showThreadName</key>
+ <value>false</value>
+ </systemProperty>
+ <systemProperty>
+ <key>org.slf4j.simpleLogger.levelInBrackets</key>
+ <value>true</value>
+ </systemProperty>
+ <systemProperty>
+ <key>org.slf4j.simpleLogger.showShortLogName</key>
+ <value>false</value>
+ </systemProperty>
+ </systemProperties>
+ </configuration>
+ </plugin>
+ <plugin> <!-- "mvn meecrowave:bake" to check out the generated website -->
+ <groupId>org.apache.meecrowave</groupId>
+ <artifactId>meecrowave-maven-plugin</artifactId>
+ <version>1.2.9</version>
+ <configuration>
+ <webapp>${project.build.directory}/documentation</webapp>
+ <webResourceCached>true</webResourceCached>
+ <reloadGoals>prepare-package</reloadGoals> <!-- mvn meecrowave:bake -DskipTests, then "r" to reload -->
+ <scanningExcludes>a,b,c,d,i,j,m,n,o,t</scanningExcludes>
+ </configuration>
+ </plugin>
+ <plugin> <!-- "mvn clean prepare-package scm-publish:publish-scm -DskipTests" to publish the website -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-scm-publish-plugin</artifactId>
+ <version>1.0-beta-2</version>
+ <executions>
+ <execution>
+ <id>scm-publish</id>
+ <phase>site-deploy</phase>
+ <goals>
+ <goal>publish-scm</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <content>${project.build.directory}/documentation</content>
+ <pubScmUrl>scm:svn:https://svn.apache.org/repos/infra/websites/production/geronimo/content/arthur</pubScmUrl>
+ <tryUpdate>true</tryUpdate>
+ <checkoutDirectory>${project.parent.basedir}/.site-content</checkoutDirectory>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <distributionManagement>
+ <repository>
+ <id>localhost</id>
+ <url>file://${project.basedir}/target/deploy/</url>
+ </repository>
+ <snapshotRepository>
+ <id>localhost</id>
+ <url>file://${project.basedir}/target/deploy/</url>
+ </snapshotRepository>
+ </distributionManagement>
+</project>
diff --git a/documentation/src/content/api.adoc b/documentation/src/content/api.adoc
new file mode 100644
index 0000000..0ba0bed
--- /dev/null
+++ b/documentation/src/content/api.adoc
@@ -0,0 +1,65 @@
+////
+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.
+////
+= Arthur API
+
+Arthur API enables you to mark in your code some Graal metainformation.
+Under the hood it just uses a meta-extension linked to this specific API.
+
+For more information about extensions, see link:spi.html[SPI] documentation.
+
+== Dependencies
+
+[source,xml]
+----
+<dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>arthur-api</artifactId>
+ <version>${project.version}</version>
+</dependency>
+----
+
+== Usage
+
+`@Register*` annotation enables to register reflection and resources on your classes directly.
+
+Example:
+
+[source,java]
+----
+@RegisterClass(all = true) <1>
+public class All {
+ private String name;
+
+ public All() {
+ // no-op
+ }
+
+ public All(final String name) {
+ this.name = name;
+ }
+
+ public boolean isAll() {
+ return true;
+ }
+}
+----
+
+<1> will register the public constructors, fields and methods as available in the native image.
+
+---
+
+Previous: link:documentation.html[Documentation] Next: link:spi.html[Arthur SPI]
diff --git a/documentation/src/content/community.adoc b/documentation/src/content/community.adoc
new file mode 100644
index 0000000..1a9120d
--- /dev/null
+++ b/documentation/src/content/community.adoc
@@ -0,0 +1,35 @@
+////
+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.
+////
+= Community
+
+=== The Source Code
+
+Source code is available on link:https://gitbox.apache.org/repos/asf?p=geronimo-arthur.git[gitbox].
+
+=== Social Media: cause contributing is not only about coding
+
+- link:https://twitter.com/ASFGeronimo[icon:twitter[] Twitter]
+- link:https://webchat.freenode.net/?channels=geronimo[icon:terminal[] IRC]
+
+=== Mailing lists
+
+* link:mailto:user-subscribe@geronimo.apache.org[Subscribe] to user@geronimo.apache.org
+* link:mailto:user-unsubscribe@geronimo.apache.org[Unsubscribe] from user@geronimo.apache.org
+* link:mailto:user@geronimo.apache.org[Post a message] on user@geronimo.apache.org
+
+Archives can be found on link:http://apache-geronimo.328035.n3.nabble.com/[Nabble].
+
diff --git a/documentation/src/content/documentation.adoc b/documentation/src/content/documentation.adoc
new file mode 100644
index 0000000..c2af88f
--- /dev/null
+++ b/documentation/src/content/documentation.adoc
@@ -0,0 +1,28 @@
+////
+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.
+////
+= Arthur Documentation
+
+Arthur project is composed of multiple module:
+
+- link:api.html[API]: it contains some end user API integrated with built-in extensions to simplify application graal-ification,
+- link:spi.html[SPI]: it contains the extension API intended to be used by libraries or integrators to simplify the graal-ification of a coding pattern or framework coding style,
+- link:implementation.html[Implementation]: it does the orchestration of the extensions execution before the `native-image` command line generation and execution,
+- link:maven.html[Maven Plugin]: it wraps the Implementation in a Maven Plugin and provides Docker helper mojos.
+
+---
+
+Previous: link:index.html[Index] Next: link:api.html[Arthur API]
diff --git a/documentation/src/content/downloads.adoc b/documentation/src/content/downloads.adoc
new file mode 100644
index 0000000..2e9ce6e
--- /dev/null
+++ b/documentation/src/content/downloads.adoc
@@ -0,0 +1,20 @@
+////
+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.
+////
+= Arthur Downloads
+
+Arthur is available on Apache link:https://repository.apache.org/content/repositories/snapshots[snapshots] repository.
+Once released it will be available on central.
diff --git a/documentation/src/content/implementation.adoc b/documentation/src/content/implementation.adoc
new file mode 100644
index 0000000..6529f14
--- /dev/null
+++ b/documentation/src/content/implementation.adoc
@@ -0,0 +1,82 @@
+////
+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.
+////
+= Arthur Implementation
+
+Arthur implementation provides `org.apache.geronimo.arthur.impl.nativeimage.ArthurNativeImageExecutor` which executes the extensions and run `native-image`.
+
+== Dependencies
+
+[source,xml]
+----
+<dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>arthur-impl</artifactId>
+ <version>${project.version}</version>
+</dependency>
+----
+
+== Usage
+
+By itself implementation module will miss a class finder and a JSON serializer.
+You will likely want to add these dependencies to be able to use it in standalone mode - maven plugin does it for you:
+
+[source,xml]
+----
+<dependency>
+ <groupId>org.apache.xbean</groupId>
+ <artifactId>xbean-finder-shaded</artifactId>
+</dependency>
+<dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-json_1.1_spec</artifactId>
+</dependency>
+<dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jsonb_1.0_spec</artifactId>
+</dependency>
+<dependency>
+ <groupId>org.apache.johnzon</groupId>
+ <artifactId>johnzon-jsonb</artifactId>
+</dependency>
+----
+
+Once you have that you can create a `Jsonb` instance to serialize the model and an `AnnotationFinder` to let `Context#finder` be implemented:
+
+[source,java]
+----
+try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+ .setProperty("johnzon.cdi.activated", false)
+ .withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL))) {
+ final AnnotationFinder finder = new AnnotationFinder(createScannedArchive());
+ new ArthurNativeImageExecutor(
+ ArthurNativeImageExecutor.ExecutorConfiguration.builder()
+ .jsonSerializer(jsonb::toJson)
+ .finder(finder::findAnnotatedClasses)
+ .configuration(configuration)
+ .workingDirectory(workdir.toPath().resolve("generated_configuration"))
+ .build())
+ .run();
+} catch (final Exception e) {
+ throw new IllegalStateException(e);
+} finally {
+ thread.setContextClassLoader(oldLoader);
+}
+----
+
+---
+
+Previous: link:spi.html[Arthur SPI] Next: link:maven.html[Arthur Maven Plugin]
diff --git a/documentation/src/content/index.adoc b/documentation/src/content/index.adoc
new file mode 100644
index 0000000..06d51c0
--- /dev/null
+++ b/documentation/src/content/index.adoc
@@ -0,0 +1,23 @@
+////
+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.
+////
+= Apache Geronimo Arthur
+
+== GraalVM for everyone
+
+Apache Geronimo Arthur is an effort to buil a thin layer on top of Oracle GraalVM to build native binaries from your Java programs.
+
+Learn more on our link:documentation.html[documentation].
diff --git a/documentation/src/content/maven.adoc b/documentation/src/content/maven.adoc
new file mode 100644
index 0000000..0950220
--- /dev/null
+++ b/documentation/src/content/maven.adoc
@@ -0,0 +1,315 @@
+////
+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.
+////
+= Apache Geronimo Arthur Maven
+
+Arthur Maven plugin provides a way to generate `native-image` configuration and execute it.
+It also enables to automatically download GraalVM avoiding you to manage the installation part.
+
+== Get started in 5 minutes
+
+First thing to do to enable a native build of your application is to add the plugin with your application entry point (`main(String[])`):
+
+[source,xml]
+----
+<plugin>
+ <groupId>org.apache.geronimo.arthur</groupId>
+ <artifactId>arthur-maven-plugin</artifactId>
+ <version>${arthur.version}</version>
+ <configuration>
+ <main>org.kamelot.Florida</main> <1>
+ </configuration>
+</plugin>
+----
+
+<1> The application to compile natively
+
+Once it is done, you can run `mvn [process-classes] arthur:native-image`.
+
+== Graal Setup
+
+You probably notices that in previous part we didn't mention you need to set your `JAVA_HOME` to a Graal instance or so.
+This is because the plugin is able to download GraalVM if it is not already done and to install - using GraalVM `gu` tool - `native-image`extension.
+You will find all the details of that feature in the configuration section but it is important to note a few points:
+
+1. You can explicit the `native-image` instance to use and avoid the implicit installation setting the configuration `nativeImage`,
+2. GraalVM version is configurable (note that it relies on SDKMan by default so ensure the last version you want to upgrade immediately is available),
+3. The plugin caches the GraalVM archive and its unpack flavor in your local maven repository to avoid to download and explode it each time.
+
+== Execution example
+
+Here is a dump of a sample execution:
+
+[source]
+----
+$ mvn arthur:native-image -o
+[INFO] Scanning for projects...
+[INFO]
+[INFO] ---------------------< org.apache.geronimo.arthur:sample >---------------------
+[INFO] Building Arthur :: Sample 1.0.0-SNAPSHOT
+[INFO] --------------------------------[ jar ]---------------------------------
+[INFO]
+[INFO] --- arthur-maven-plugin:1.0.0-SNAPSHOT:native-image (default-cli) @ jdbc ---
+[INFO] Using GRAAL: /home/rmannibucau/.m2/repository/org/apache/geronimo/arthur/cache/graal/19.2.1/distribution_exploded <1>
+[INFO] Extension org.apache.geronimo.arthur.maven.extension.MavenArthurExtension updated build context <2>
+[INFO] Creating resources model '/opt/dev/arthur/sample/target/arthur_workdir/generated_configuration/resources.arthur.json' <3>
+[INFO] Creating dynamic proxy model '/opt/dev/arthur/sample/target/arthur_workdir/generated_configuration/dynamicproxies.arthur.json' <3>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] classlist: 6,427.14 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] (cap): 2,126.93 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] setup: 3,480.51 ms <4>
+ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider com.oracle.truffle.js.scriptengine.GraalJSEngineFactory could not be instantiated
+ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider com.oracle.truffle.js.scriptengine.GraalJSEngineFactory could not be instantiated
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] (typeflow): 15,605.58 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] (objects): 11,369.56 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] (features): 1,901.64 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] analysis: 30,057.86 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] (clinit): 568.59 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] universe: 1,196.02 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] (parse): 2,334.15 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] (inline): 3,093.09 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] (compile): 20,410.16 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] compile: 27,352.51 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] image: 2,792.40 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] write: 677.26 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697] [total]: 72,280.13 ms <4>
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 01:14 min
+[INFO] Finished at: 2019-10-29T17:55:16+01:00
+[INFO] ------------------------------------------------------------------------
+
+----
+
+<1> The GraalVM distribution was already existing so was not download and was directly used,
+<2> Maven inline Graal configuration (resources, reflection, bundles) was set up,
+<3> Dynamic (Arthur prebuild phase) configuration was dumped before launching `native-image`,
+<4> `native-image` execution/phases
+
+== Native Image Mojo configuration
+
+The plugin is quite configurable and even enable to build a main in a test scope.
+Here is the full list of available configuration.
+
+include::{generated_dir}/generated_nativeimage_mojo.adoc[]
+
+== What about docker?
+
+One of the main reasons to go native is to reduce the startup latency and the memory consumption.
+This is literally a paraphrase to say we want to run into Kubernetes.
+Therefore the question to bundle the native binary as a docker image comes pretty quickly.
+
+=== Sample Dockerfile
+
+There are multiple option to create a docker image but one interesting point to mention is to build the binary in a docker image to ensure the target binary matches the target image architecture.
+For that case, we recommend you to build your project as you want, copy the project in a builder docker image and use a multi-stage builder to ensure you build on the platform you will run (the `FROM` of your `Dockerfile`).
+
+TIP: to avoid a lot of downloads/latency it can be neat to *not* bind `arthur:native-image` plugin goal to any phase and just call it explicitly in your build. To do that just use `<phase />` instead of an explicit phase if you define a custom execution.
+
+Here is an example:
+
+[source,Dockerfile]
+----
+FROM maven:3.6-jdk-8-slim AS builder
+COPY . /project
+COPY target/m2 /root/.m2/repository
+WORKDIR /project
+RUN mvn package arthur:native-image
+
+FROM scratch
+COPY --from=builder /project/target/myapp.graal.bin /project/myapp.graal.bin
+ENTRYPOINT [ "/project/myapp.graal.bin" ]
+----
+
+To avoid to download all needed dependencies all the time don't forget to prepare a local copy of the maven repository, here is how to launch this docker creation assuming you put this `Dockerfile` at the root of your maven project:
+
+[source,sh]
+----
+# prepare the local copy of the m2 with dependency plugin (for examples, some other plugins will also include build maven plugins in the dedicated m2)
+mvn dependency:copy-dependencies -Dmdep.useRepositoryLayout=true -DoutputDirectory=target/m2
+
+# actually build
+docker build -t sample:latest
+----
+
+TIP: you can also do the package on your machine and skip it in the docker build but the tip to prepare a local m2 copy is still helping to speed up the build.
+
+TIP: if you use private repositories, don't forget to copy your settings.xml as well (in `/root/.m2` for `maven`/builder image).
+
+IMPORTANT: when building on a jenkins running on Kubernetes, ensure to use a build image with an architecture compatible, this avoids all that setup and you can just run it directly as if it was locally.
+
+=== Jib to create an image
+
+Jib is an awesome project propulsed by Google enabling to build docker images without docker.
+It can be used to put your binary into a very light image (based on the "empty" `scratch` one). Here is how to add it to your pom:
+
+[source,xml]
+----
+<plugin> <1>
+ <groupId>com.google.cloud.tools</groupId>
+ <artifactId>jib-maven-plugin</artifactId>
+ <version>1.7.0</version>
+ <configuration>
+ <from>
+ <image>scratch</image> <2>
+ </from>
+ <to>
+ <image>sample:latest</image>
+ </to>
+ <extraDirectories>
+ <paths>
+ <path>${project.build.directory}/graal</path> <3>
+ </paths>
+ <permissions>
+ <permission> <4>
+ <file>/sample.graal.bin</file>
+ <mode>755</mode>
+ </permission>
+ </permissions>
+ </extraDirectories>
+ <container>
+ <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
+ <entrypoint>
+ <arg>/sample.graal.bin</arg> <5>
+ </entrypoint>
+ </container>
+ </configuration>
+</plugin>
+----
+
+<1> Add `jib-maven-plugin` in your `pom.xml`
+<2> Use `scratch` keyword as base image (which almost means "nothing" or "empty image")
+<3> Reference the folder containing your binary (you can desire to tune the `output`parameter of the `arthur-maven-plugin` to ensure the folder only contains your binary since the full folder will be added to the image)
+<4> Ensure the binary has the right permission (executable)
+<5> Set the binary as entrypoint
+
+Then you can create your docker image just launching: `mvn jib:dockerBuild`.
+
+TIP: you can also directly push to a remote repository without the need of having a local docker daemon, see Jib documentation for more details.
+
+IMPORTANT: in current jib version, the dependencies are build artifacts are still added to the image so you can have some "dead" code with that option.
+To avoid it you can switch to `arthur:docker` or `arthur:image` goals.
+
+=== Arthur Docker
+
+`arthur-maven-plugin` enables to build a docker image with a plain binary built with `native-image`.
+Its usage is close to jib - with a simpler configuration - but it also can be combine with `native-image` goal:
+
+[source,bash]
+----
+mvn arthur:native-image arthur:docker
+----
+
+Output looks like:
+
+[source]
+----
+$ mvn arthur:native-image arthur:docker
+[INFO] Scanning for projects...
+[INFO]
+[INFO] ---------------------< org.apache.geronimo.Arthur:sample >---------------------
+[INFO] Building Arthur :: Sample 1.0.0-SNAPSHOT
+[INFO] --------------------------------[ jar ]---------------------------------
+[INFO]
+[INFO] --- arthur-maven-plugin:1.0.0-SNAPSHOT:native-image (default-cli) @ sample ---
+[INFO] Using GRAAL: /home/rmannibucau/.m2/repository/org/apache/geronimo/arthur/cache/graal/19.2.1/distribution_exploded
+[INFO] Extension org.apache.geronimo.arthur.maven.extension.MavenArthurExtension updated build context
+[INFO] Creating resources model '/media/data/home/rmannibucau/1_dev/connectors-se/jdbc/target/arthur_workdir/generated_configuration/resources.arthur.json'
+[INFO] Creating dynamic proxy model '/media/data/home/rmannibucau/1_dev/connectors-se/jdbc/target/arthur_workdir/generated_configuration/dynamicproxies.arthur.json'
+...same as before
+[INFO]
+[INFO] --- arthur-maven-plugin:1.0.0-SNAPSHOT:docker (default-cli) @ sample ---
+[INFO] Containerizing application with the following files:
+[INFO] Binary:
+[INFO] /opt/geronimo/arthur/sample/target/sample.graal.bin
+[INFO] Getting scratch base image...
+[INFO] Building Binary layer...
+[INFO]
+[INFO] Container entrypoint set to [/sample]
+[INFO] Container program arguments set to []
+[INFO] Loading to Docker daemon...
+[INFO] Built 'sample:1.0.0-SNAPSHOT'
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 01:29 min
+[INFO] Finished at: 2019-10-30T10:51:43+01:00
+[INFO] ------------------------------------------------------------------------
+----
+
+==== Configuration
+
+include::{generated_dir}/generated_docker_mojo.adoc[]
+
+=== Arthur Image
+
+Similarly to docker goal, the plugin can generate an image.
+To do it just replace the `arthur:docker` by `arthur:image`.
+
+TIP: this goal does *not* need a docker daemon on the machine, it just uses HTTP(S) communication with a registry.
+
+==== Configuration
+
+include::{generated_dir}/generated_image_mojo.adoc[]
+
+== Advanced example
+
+Just to give a real world configuration, here is how a simple JDBC application can be natified.
+It configure some resource bundle, setup h2 driver and exclude derby from the build classpath:
+
+[source,xml]
+----
+<plugin>
+ <groupId>org.apache.geronimo.arthur</groupId>
+ <artifactId>arthur-maven-plugin</artifactId>
+ <version>${arthur.version}</version>
+ <configuration>
+ <main>org.talend.components.jdbc.graalvm.MainTableNameInputEmitter</main>
+ <supportTestArtifacts>true</supportTestArtifacts> <!-- for demo only -->
+ <initializeAtBuildTime>
+ <initializeAtBuildTime>org.h2.Driver</initializeAtBuildTime>
+ <initializeAtBuildTime>org.company.MyProxyInterface1</initializeAtBuildTime>
+ <initializeAtBuildTime>org.company.MyProxyInterface2</initializeAtBuildTime>
+ </initializeAtBuildTime>
+ <bundles>
+ <bundle>
+ <name>org.company.MyProxyInterface1Messages</name>
+ </bundle>
+ </bundles>
+ <dynamicProxies>
+ <dynamicProxy> <!-- short notation -->
+ <classes>org.company.MyProxyInterface1</classes>
+ </dynamicProxy>
+ <dynamicProxy>
+ <classes> <!-- long notation -->
+ <class>org.company.MyProxyInterface2</class>
+ </classes>
+ </dynamicProxy>
+ </dynamicProxies>
+ <excludedArtifacts> <!-- this setup uses supportTestArtifacts but we assume we don't want derby which is used in test dependencies -->
+ <excludedArtifact>org.apache.derby:derby</excludedArtifact>
+ <excludedArtifact>org.apache.derby:derbyclient</excludedArtifact>
+ <excludedArtifact>org.apache.derby:derbynet</excludedArtifact>
+ </excludedArtifacts>
+ </configuration>
+</plugin>
+----
+
+Then you can just run `mvn arthur:native-image arthur:docker` to get a ready to deploy image.
+
+---
+
+Previous: link:implementation.html[Arthur Implementation]
diff --git a/documentation/src/content/spi.adoc b/documentation/src/content/spi.adoc
new file mode 100644
index 0000000..c36393e
--- /dev/null
+++ b/documentation/src/content/spi.adoc
@@ -0,0 +1,58 @@
+////
+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.
+////
+= Arthur SPI
+
+Arthur SPI enables you to add custom logic to generate `native-image` configuration.
+
+== Dependencies
+
+[source,xml]
+----
+<dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>arthur-spi</artifactId>
+ <version>${project.version}</version>
+</dependency>
+----
+
+== Usage
+
+You must implement a `org.apache.geronimo.arthur.spi.ArthurExtension` and register it as a SPI (fully qualified name in `META-INF/services/org.apache.geronimo.arthur.spi.ArthurExtension`).
+This extension will have to implement `execute(Context)` method to register reflection, resource, ... metadata needed by `native-image` build.
+
+Here is an example:
+
+[source,java]
+----
+public class MyJaxbExtension implements ArthurExtension {
+
+ @Override
+ public void execute(final Context context) {
+ context.findAnnotatedClasses(XmlRootElement.class).stream()
+ .flatMap(clazz -> createReflectionModel(clazz))
+ .forEach(context::register);
+ }
+
+ // createReflectionModel() just instantiate a ClassReflectionModel instance
+}
+----
+
+TIP: you can use `context.finder()` to find classes based on some annotation.
+
+---
+
+Previous: link:api.html[Arthur API] Next: link:implementation.html[Arthur Implementation]
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/DocumentationGeneratorLauncher.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/DocumentationGeneratorLauncher.java
new file mode 100644
index 0000000..45d0832
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/DocumentationGeneratorLauncher.java
@@ -0,0 +1,85 @@
+/*
+ * 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.documentation;
+
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+import static lombok.AccessLevel.PRIVATE;
+
+import java.beans.PropertyEditorManager;
+import java.io.PrintStream;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.apache.geronimo.arthur.documentation.editor.PathEditor;
+import org.apache.geronimo.arthur.documentation.io.ConsumedPrintStream;
+import org.apache.geronimo.arthur.documentation.io.FolderVisitor;
+import org.apache.geronimo.arthur.documentation.lang.PathPredicates;
+import org.apache.geronimo.arthur.documentation.mojo.MojoParser;
+import org.apache.geronimo.arthur.documentation.renderer.AsciidocRenderer;
+import org.tomitribe.crest.Main;
+import org.tomitribe.crest.environments.Environment;
+import org.tomitribe.crest.environments.SystemEnvironment;
+
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@NoArgsConstructor(access = PRIVATE)
+public final class DocumentationGeneratorLauncher {
+ public static void main(final String[] args) throws Exception {
+ final Map<Class<?>, Object> services = createServices().collect(toMap(Object::getClass, identity()));
+ final SystemEnvironment env = new SystemEnvironment(services) {
+ private final ConsumedPrintStream err = new ConsumedPrintStream(log::error);
+ private final ConsumedPrintStream out = new ConsumedPrintStream(log::info);
+
+ @Override
+ public PrintStream getOutput() {
+ return out;
+ }
+
+ @Override
+ public PrintStream getError() {
+ return err;
+ }
+ };
+ PropertyEditorManager.registerEditor(Path.class, PathEditor.class);
+ Environment.ENVIRONMENT_THREAD_LOCAL.set(env);
+ try {
+ new Main().main(env, args);
+ } finally {
+ // cheap cleanup solution
+ services.values().stream()
+ .filter(AutoCloseable.class::isInstance)
+ .map(AutoCloseable.class::cast)
+ .forEach(it -> {
+ try {
+ it.close();
+ } catch (final Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ });
+ Environment.ENVIRONMENT_THREAD_LOCAL.remove();
+ }
+ }
+
+ private static Stream<Object> createServices() {
+ final PathPredicates predicates = new PathPredicates();
+ return Stream.of(predicates, new AsciidocRenderer(), new FolderVisitor(predicates), new MojoParser());
+ }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/command/Generate.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/command/Generate.java
new file mode 100644
index 0000000..8c0f1b6
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/command/Generate.java
@@ -0,0 +1,292 @@
+/*
+ * 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.documentation.command;
+
+import static java.util.Comparator.comparing;
+import static java.util.Locale.ROOT;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.joining;
+import static org.asciidoctor.SafeMode.UNSAFE;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.geronimo.arthur.documentation.io.FolderConfiguration;
+import org.apache.geronimo.arthur.documentation.io.FolderVisitor;
+import org.apache.geronimo.arthur.documentation.mojo.MojoParser;
+import org.apache.geronimo.arthur.documentation.renderer.AsciidocRenderer;
+import org.apache.geronimo.arthur.documentation.renderer.TemplateConfiguration;
+import org.asciidoctor.AttributesBuilder;
+import org.asciidoctor.Options;
+import org.asciidoctor.OptionsBuilder;
+import org.tomitribe.crest.api.Command;
+import org.tomitribe.crest.api.Default;
+import org.tomitribe.crest.api.Defaults.DefaultMapping;
+import org.tomitribe.crest.api.Err;
+import org.tomitribe.crest.api.Option;
+import org.tomitribe.crest.api.Out;
+
+public class Generate {
+ @Command
+ public void generate(
+ @DefaultMapping(name = "location", value = "src/content")
+ @DefaultMapping(name = "includes", value = "[.+/]?.+\\.adoc$")
+ @Option("content-") final FolderConfiguration contentConfiguration,
+
+ @DefaultMapping(name = "location", value = "src/static")
+ @Option("static-") final FolderConfiguration staticConfiguration,
+
+ @DefaultMapping(name = "header", value = "src/template/header.html")
+ @DefaultMapping(name = "footer", value = "src/template/footer.html")
+ @DefaultMapping(name = "nav", value = "src/template/nav.adoc")
+ @Option("template-") final TemplateConfiguration templateConfiguration,
+
+ @Option("mojo") final List<Path> mojos,
+ @Option("output") final Path output,
+ @Option("work-directory") final Path workdir,
+
+ @Option("threads") @Default("${sys.processorCount}") final int threads,
+
+ @Out final PrintStream stdout,
+ @Err final PrintStream stderr,
+
+ final AsciidocRenderer renderer,
+ final FolderVisitor visitor,
+ final MojoParser mojoParser) {
+ stdout.println("Generating the website in " + output);
+
+ final Collection<Throwable> errors = new ArrayList<>();
+ final Executor executorImpl = threads > 1 ? newThreadPool(output, threads) : Runnable::run;
+ final Executor executor = task -> {
+ try {
+ task.run();
+ } catch (final Throwable err) {
+ err.printStackTrace(stderr);
+ synchronized (errors) {
+ errors.add(err);
+ }
+ }
+ };
+
+ final CountDownLatch templateLatch = new CountDownLatch(1);
+ final AtomicReference<BiFunction<String, String, String>> computedTemplatization = new AtomicReference<>();
+ final Supplier<BiFunction<String, String, String>> templatize = () -> {
+ try {
+ templateLatch.await();
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ return computedTemplatization.get();
+ };
+
+ final Options adocOptions = OptionsBuilder.options()
+ .safe(UNSAFE) // we generated_dir is not safe but locally it is ok
+ .attributes(AttributesBuilder.attributes()
+ .attribute("icons", "font")
+ .attribute("generated_dir", workdir.toAbsolutePath().toString()))
+ .get();
+
+ executor.execute(() -> {
+ try {
+ computedTemplatization.set(compileTemplate(templateConfiguration));
+ } finally {
+ templateLatch.countDown();
+ }
+ });
+ executor.execute(() -> mojos.forEach(mojo -> {
+ try {
+ generateMojoDoc(
+ mojo.getFileName().toString().toLowerCase(ROOT).replace("mojo.java", ""),
+ mojoParser, mojo, workdir, stdout);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }));
+
+ final CountDownLatch visitorsFinished = new CountDownLatch(2);
+ try {
+ executor.execute(() -> {
+ try {
+ visitor.visit(contentConfiguration, file -> executor.execute(() -> {
+ final String name = file.getFileName().toString();
+ final int dot = name.lastIndexOf('.');
+ final String targetFilename = dot > 0 ? name.substring(0, dot) + ".html" : name;
+ final Path targetFolder = contentConfiguration.getLocation()
+ .relativize(file)
+ .getParent();
+ final Path target = output.resolve(targetFolder == null ?
+ Paths.get(targetFilename) :
+ targetFolder.resolve(targetFilename));
+ ensureExists(target.getParent());
+ try {
+ final String read = read(file);
+ final Map<String, String> metadata = renderer.extractMetadata(read);
+ Files.write(
+ target,
+ templatize.get().apply(
+ metadata.getOrDefault("title", "Arthur"),
+ renderer.render(read, adocOptions))
+ .getBytes(StandardCharsets.UTF_8),
+ StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ stdout.println("Created '" + target + "'");
+ }));
+ } finally {
+ visitorsFinished.countDown();
+ }
+ });
+ executor.execute(() -> {
+ try {
+ visitor.visit(staticConfiguration, file -> executor.execute(() -> {
+ final Path target = output.resolve(staticConfiguration.getLocation().relativize(file));
+ ensureExists(target.getParent());
+ try {
+ Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ stdout.println("Copied '" + target + "'");
+ }));
+ } finally {
+ visitorsFinished.countDown();
+ }
+ });
+ } finally {
+ try {
+ visitorsFinished.await();
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ if (ExecutorService.class.isInstance(executorImpl)) {
+ final ExecutorService service = ExecutorService.class.cast(executorImpl);
+ service.shutdown();
+ try {
+ if (!service.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS)) {
+ stderr.println("Exiting without the executor being properly shutdown");
+ }
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ if (!errors.isEmpty()) {
+ final IllegalStateException failed = new IllegalStateException("Execution failed");
+ errors.forEach(failed::addSuppressed);
+ throw failed;
+ }
+
+ stdout.println("Website generation done");
+ }
+
+ private void generateMojoDoc(final String marker, final MojoParser mojoParser, final Path mojo, final Path workdir,
+ final PrintStream stdout) throws IOException {
+ ensureExists(workdir);
+ final Collection<MojoParser.Parameter> parameters = mojoParser.extractParameters(mojo);
+ try (final Writer writer = Files.newBufferedWriter(workdir.resolve("generated_" + marker + "_mojo.adoc"))) {
+ writer.write("[opts=\"header\",role=\"table table-bordered\"]\n" +
+ "|===\n" +
+ "|Name|Type|Description\n\n" +
+ parameters.stream()
+ .sorted(comparing(MojoParser.Parameter::getName))
+ .map(this::toLine)
+ .collect(joining("\n\n")) +
+ "\n|===\n");
+ }
+ stdout.println("Generated documentation for " + mojo);
+ }
+
+ private String toLine(final MojoParser.Parameter parameter) {
+ return "|" + parameter.getName() + (parameter.isRequired() ? "*" : "") +
+ "\n|" + parameter.getType() +
+ "\na|\n" + parameter.getDescription() +
+ "\n\n*Default value*: " + parameter.getDefaultValue() +
+ "\n\n*User property*: " + parameter.getProperty();
+ }
+
+ private String read(final Path file) {
+ try {
+ return Files.lines(file).collect(joining("\n"));
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private BiFunction<String, String, String> compileTemplate(final TemplateConfiguration templateConfiguration) {
+ final Collection<Function<String, String>> parts = new ArrayList<>();
+
+ final Path header = templateConfiguration.getHeader();
+ if (header != null && Files.exists(header)) {
+ final String content = read(header);
+ parts.add(s -> content + s);
+ }
+
+ final Path footer = templateConfiguration.getFooter();
+ if (footer != null && Files.exists(footer)) {
+ final String content = read(footer);
+ parts.add(s -> s + content);
+ }
+ final Function<String, String> fn = parts.stream().reduce(identity(), Function::andThen);
+ return (title, html) -> fn.apply(html).replace("${arthurTemplateTitle}", title);
+ }
+
+ private ExecutorService newThreadPool(@Option("output") Path output, @Default("${sys.processorCount}") @Option("threads") int threads) {
+ return new ThreadPoolExecutor(threads, threads, 1, MINUTES, new LinkedBlockingQueue<>(), new ThreadFactory() {
+ private final AtomicInteger counter = new AtomicInteger();
+
+ @Override
+ public Thread newThread(final Runnable worker) {
+ return new Thread(worker, "arthur-generator-" + counter.incrementAndGet() + "-[" + output + "]");
+ }
+ });
+ }
+
+ private void ensureExists(final Path dir) {
+ if (!Files.exists(dir)) {
+ try {
+ Files.createDirectories(dir);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/editor/PathEditor.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/editor/PathEditor.java
new file mode 100644
index 0000000..956e58a
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/editor/PathEditor.java
@@ -0,0 +1,32 @@
+/*
+ * 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.documentation.editor;
+
+import java.nio.file.Paths;
+
+import org.tomitribe.util.editor.AbstractConverter;
+
+public class PathEditor extends AbstractConverter {
+ @Override
+ protected Object toObjectImpl(final String text) {
+ return Paths.get(text);
+ }
+
+ protected String toStringImpl(final Object value) {
+ return String.valueOf(value);
+ }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/interpolation/Sys.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/interpolation/Sys.java
new file mode 100644
index 0000000..667444e
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/interpolation/Sys.java
@@ -0,0 +1,34 @@
+/*
+ * 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.documentation.interpolation;
+
+import java.lang.reflect.Method;
+
+import org.tomitribe.crest.cmds.targets.Target;
+import org.tomitribe.crest.contexts.DefaultsContext;
+
+public class Sys implements DefaultsContext {
+ @Override
+ public String find(final Target target, final Method method, final String key) {
+ switch (key) {
+ case ".processorCount":
+ return Integer.toString(Math.max(1, Runtime.getRuntime().availableProcessors()));
+ default:
+ return null;
+ }
+ }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/ConsumedPrintStream.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/ConsumedPrintStream.java
new file mode 100644
index 0000000..d39ec31
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/ConsumedPrintStream.java
@@ -0,0 +1,102 @@
+/*
+ * 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.documentation.io;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.function.Consumer;
+
+public class ConsumedPrintStream extends PrintStream {
+ private final Consumer<String> consumer;
+
+ private final ByteArrayOutputStream buffer;
+
+ public ConsumedPrintStream(final Consumer<String> consumer) {
+ super(new ByteArrayOutputStream());
+ this.consumer = consumer;
+ this.buffer = ByteArrayOutputStream.class.cast(out);
+ }
+
+ private void onEnd() {
+ final byte[] bytes = buffer.toByteArray();
+ if (bytes.length > 0) {
+ consumer.accept(new String(bytes, 0, bytes[bytes.length - 1] == '\n' ? bytes.length - 1 : bytes.length, StandardCharsets.UTF_8));
+ buffer.reset();
+ }
+ }
+
+ @Override
+ public void println(final String content) {
+ super.println(content);
+ onEnd();
+ }
+
+ @Override
+ public void println() {
+ super.println();
+ onEnd();
+ }
+
+ @Override
+ public void println(final boolean x) {
+ super.println(x);
+ onEnd();
+ }
+
+ @Override
+ public void println(final char x) {
+ super.println(x);
+ onEnd();
+ }
+
+ @Override
+ public void println(final int x) {
+ super.println(x);
+ onEnd();
+ }
+
+ @Override
+ public void println(final long x) {
+ super.println(x);
+ onEnd();
+ }
+
+ @Override
+ public void println(final float x) {
+ super.println(x);
+ onEnd();
+ }
+
+ @Override
+ public void println(final double x) {
+ super.println(x);
+ onEnd();
+ }
+
+ @Override
+ public void println(final char[] x) {
+ super.println(x);
+ onEnd();
+ }
+
+ @Override
+ public void println(final Object x) {
+ super.println(x);
+ onEnd();
+ }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderConfiguration.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderConfiguration.java
new file mode 100644
index 0000000..281f680
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ * 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.documentation.io;
+
+import java.nio.file.Path;
+import java.util.List;
+
+import org.tomitribe.crest.api.Default;
+import org.tomitribe.crest.api.Option;
+import org.tomitribe.crest.api.Options;
+
+import lombok.Data;
+
+@Data
+@Options
+public class FolderConfiguration {
+ private final Path location;
+ private final List<String> includes;
+ private final List<String> excludes;
+
+ public FolderConfiguration(@Option("location") final Path location,
+ @Option("includes") final List<String> includes,
+ @Option("excludes") @Default("^\\..+") final List<String> excludes) {
+ this.location = location;
+ this.includes = includes;
+ this.excludes = excludes;
+ }
+}
\ No newline at end of file
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderVisitor.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderVisitor.java
new file mode 100644
index 0000000..fdbfc00
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderVisitor.java
@@ -0,0 +1,58 @@
+/*
+ * 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.documentation.io;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import org.apache.geronimo.arthur.documentation.lang.PathPredicates;
+
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class FolderVisitor {
+ private final PathPredicates predicates;
+
+ public void visit(final FolderConfiguration configuration,
+ final Consumer<Path> consumer) {
+ final Path root = requireNonNull(configuration.getLocation(), "Missing location");
+ if (Files.exists(root)) {
+ final Predicate<Path> filter = predicates.createFilter(configuration.getIncludes(), configuration.getExcludes());
+ try {
+ Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+ if (filter.test(root.relativize(file))) {
+ consumer.accept(file);
+ }
+ return super.visitFile(file, attrs);
+ }
+ });
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/lang/PathPredicates.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/lang/PathPredicates.java
new file mode 100644
index 0000000..0309c0c
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/lang/PathPredicates.java
@@ -0,0 +1,38 @@
+/*
+ * 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.documentation.lang;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+public class PathPredicates {
+ public Predicate<Path> createFilter(final List<String> includes, final List<String> excludes) {
+ return createFilter(includes).and(createFilter(excludes).negate());
+ }
+
+ public Predicate<Path> createFilter(final List<String> patterns) {
+ return patterns == null || patterns.isEmpty() ?
+ p -> true :
+ patterns.stream()
+ .map(Pattern::compile)
+ .map(Pattern::asPredicate)
+ .map(pattern -> (Predicate<Path>) path -> pattern.test(path.toString()))
+ .reduce(p -> false, Predicate::or);
+ }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/mojo/MojoParser.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/mojo/MojoParser.java
new file mode 100644
index 0000000..2017fd2
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/mojo/MojoParser.java
@@ -0,0 +1,121 @@
+/*
+ * 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.documentation.mojo;
+
+import static java.util.stream.Collectors.toList;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import lombok.Data;
+
+public class MojoParser {
+ // simplified java parser for *our* code
+ public Collection<Parameter> extractParameters(final Path file) throws IOException {
+ final Collection<Parameter> parameters = new ArrayList<>();
+ String parent = null;
+ try (final BufferedReader reader = Files.newBufferedReader(file)) {
+ String line;
+ boolean jdoc = false;
+ final StringBuilder javadoc = new StringBuilder();
+ while ((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.startsWith("public class ") || line.startsWith("public abstract class ")) {
+ final int ext = line.indexOf("extends ");
+ if (ext > 0) {
+ parent = line.substring(ext + "extends ".length(), line.indexOf("{")).trim();
+ }
+ if ("AbstractMojo".equals(parent)) {
+ parent = null;
+ }
+ } else if (line.startsWith("/**")) {
+ jdoc = true;
+ } else if (line.endsWith("*/")) {
+ jdoc = false;
+ } else if (jdoc) {
+ javadoc.append(line.startsWith("*") ? line.substring(1).trim() : line);
+ } else if (javadoc.length() > 0 && line.startsWith("@Parameter")) {
+ final StringBuilder annotation = new StringBuilder(line);
+ while ((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.startsWith("private ") || line.startsWith("protected ")) {
+ final int nameStart = line.lastIndexOf(' ') + 1;
+ final String config = annotation.toString();
+ final int prefixLen = line.indexOf(' ') + 1;
+ String type = line.substring(prefixLen, line.indexOf(" ", prefixLen)).trim();
+ final int generics = type.indexOf('<');
+ if (generics > 0) {
+ type = type.substring(0, generics);
+ }
+ parameters.add(new Parameter(
+ line.substring(nameStart, line.length() - ";".length()),
+ type,
+ config.contains("required = true"),
+ find("property =", config),
+ javadoc.toString(),
+ find("defaultValue =", config)));
+ javadoc.setLength(0);
+ break;
+ } else {
+ annotation.append(line);
+ }
+ }
+ } else if (line.startsWith("package")) { // we have the header before
+ javadoc.setLength(0);
+ }
+ }
+ }
+ if (parent != null) {
+ return Stream.concat(
+ parameters.stream(),
+ extractParameters(file.getParent().resolve(parent + ".java")).stream())
+ .collect(toList());
+ }
+ return parameters;
+ }
+
+ private String find(final String prefix, final String value) {
+ int start = value.indexOf(prefix);
+ if (start < 0) {
+ return "-";
+ }
+ start += prefix.length();
+ while (Character.isWhitespace(value.charAt(start)) || value.charAt(start) == '"') {
+ start++;
+ }
+ final int end = value.indexOf('"', start);
+ if (end > 0) {
+ return value.substring(start, end).trim();
+ }
+ return "-";
+ }
+
+ @Data
+ public static class Parameter {
+ private final String name;
+ private final String type;
+ private final boolean required;
+ private final String property;
+ private final String description;
+ private final String defaultValue;
+ }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/AsciidocRenderer.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/AsciidocRenderer.java
new file mode 100644
index 0000000..b5a3593
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/AsciidocRenderer.java
@@ -0,0 +1,65 @@
+/*
+ * 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.documentation.renderer;
+
+import static java.util.Collections.singletonMap;
+
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import org.asciidoctor.Asciidoctor;
+import org.asciidoctor.Options;
+import org.asciidoctor.ast.DocumentHeader;
+import org.asciidoctor.jruby.internal.JRubyAsciidoctor;
+
+public class AsciidocRenderer implements AutoCloseable {
+ private Asciidoctor asciidoctor;
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ public AsciidocRenderer() {
+ new Thread(() -> { // this is insanely slow so let's do it in background
+ asciidoctor = JRubyAsciidoctor.create();
+ latch.countDown();
+ }, getClass().getName() + '-' + hashCode()).start();
+ }
+
+ public String render(final String input, final Options options) {
+ await();
+ return asciidoctor.convert(input, options);
+ }
+
+ @Override
+ public void close() {
+ await();
+ asciidoctor.shutdown();
+ }
+
+ private void await() {
+ try {
+ latch.await();
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ public Map<String, String> extractMetadata(final String content) {
+ await();
+ final DocumentHeader header = asciidoctor.readDocumentHeader(content);
+ return singletonMap("title", header.getPageTitle());
+ }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/TemplateConfiguration.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/TemplateConfiguration.java
new file mode 100644
index 0000000..d878483
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/TemplateConfiguration.java
@@ -0,0 +1,37 @@
+/*
+ * 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.documentation.renderer;
+
+import java.nio.file.Path;
+
+import org.tomitribe.crest.api.Option;
+import org.tomitribe.crest.api.Options;
+
+import lombok.Data;
+
+@Data
+@Options
+public class TemplateConfiguration {
+ private final Path header;
+ private final Path footer;
+
+ public TemplateConfiguration(@Option("header") final Path header,
+ @Option("footer") final Path footer) {
+ this.header = header;
+ this.footer = footer;
+ }
+}
diff --git a/documentation/src/main/resources/META-INF/services/org.tomitribe.crest.contexts.DefaultsContext b/documentation/src/main/resources/META-INF/services/org.tomitribe.crest.contexts.DefaultsContext
new file mode 100644
index 0000000..c88588c
--- /dev/null
+++ b/documentation/src/main/resources/META-INF/services/org.tomitribe.crest.contexts.DefaultsContext
@@ -0,0 +1 @@
+org.apache.geronimo.arthur.documentation.interpolation.Sys
diff --git a/documentation/src/main/resources/crest-commands.txt b/documentation/src/main/resources/crest-commands.txt
new file mode 100644
index 0000000..7972f67
--- /dev/null
+++ b/documentation/src/main/resources/crest-commands.txt
@@ -0,0 +1 @@
+org.apache.geronimo.arthur.documentation.command.Generate
diff --git a/documentation/src/static/css/arthur.css b/documentation/src/static/css/arthur.css
new file mode 100644
index 0000000..372cdb0
--- /dev/null
+++ b/documentation/src/static/css/arthur.css
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+* {
+ text-align: justify;
+}
+.page-header {
+ padding-top: 100px;
+ min-height: calc(100vh - 50px);
+}
+#footer {
+ font-style: italic;
+ font-size: smaller;
+}
+table > thead > tr > th {
+ background-color: lightslategray;
+}
\ No newline at end of file
diff --git a/documentation/src/static/js/arthur.js b/documentation/src/static/js/arthur.js
new file mode 100644
index 0000000..d87d6b5
--- /dev/null
+++ b/documentation/src/static/js/arthur.js
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+(function() {
+ $('code[class^="language-"]').addClass('prettyprint');
+ $('thead').addClass('thead-light');
+ prettyPrint();
+})();
diff --git a/documentation/src/template/footer.html b/documentation/src/template/footer.html
new file mode 100644
index 0000000..95d52eb
--- /dev/null
+++ b/documentation/src/template/footer.html
@@ -0,0 +1,22 @@
+</div>
+
+<div id="footer">
+ <div class="container">
+ Copyright © 1999-2019 The Apache Software Foundation, Licensed under the Apache License, Version 2.0.
+ Apache Geronimo, Geronimo, Apache, the Apache feather logo, and the Apache Geronimo project logo are trademarks
+ of
+ The Apache Software Foundation. All other marks mentioned may be trademarks or registered trademarks of their
+ respective owners.
+ </div>
+</div>
+
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
+ integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
+<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js"
+ integrity="sha384-o+RDsa0aLu++PJvFqy8fFScvbHFLtbvScb8AjopnFD+iEQ7wo/CG0xlczd+2O/em"
+ crossorigin="anonymous"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js"
+ integrity="sha256-SHXnnZAbgSEf+OBhDLR7I2mx9vNZAIzPeCGhxRV+VQw=" crossorigin="anonymous"></script>
+<script src="js/arthur.js"></script>
+</body>
+</html>
diff --git a/documentation/src/template/header.html b/documentation/src/template/header.html
new file mode 100644
index 0000000..69c7b2d
--- /dev/null
+++ b/documentation/src/template/header.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8"/>
+ <title>${arthurTemplateTitle}</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="Apache Geronimo Arthur Site">
+ <meta name="author" content="Apache">
+ <meta name="keywords" content="Apache Geronimo Arthur">
+ <meta name="generator" content="Arthur Site Generator">
+
+ <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css" rel="stylesheet"
+ integrity="sha384-Smlep5jCw/wG7hdkwQ/Z5nLIefveQRIY9nfy6xoR1uRYBtpZgI6339F5dgvm/e9B" crossorigin="anonymous">
+ <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"
+ integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css"
+ integrity="sha256-IPtNBA1od/cGBfXTxYDxuT5+Y2BKy14o6j0FaIXDmYk=" crossorigin="anonymous"/>
+ <link href="https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/flatly/bootstrap.min.css" rel="stylesheet"
+ integrity="sha384-T5jhQKMh96HMkXwqVMSjF3CmLcL1nT9//tCqu9By5XSdj7CwR0r+F3LTzUdfkkQf" crossorigin="anonymous">
+ <link rel="stylesheet" href="css/arthur.css"/>
+
+ <!--[if lt IE 9]>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"
+ integrity="sha256-3Jy/GbSLrg0o9y5Z5n1uw0qxZECH7C6OQpVBgNFYa0g=" crossorigin="anonymous"></script>
+ <![endif]-->
+</head>
+<body cz-shortcut-listen="true">
+
+<div class="navbar navbar-expand-lg fixed-top navbar-dark bg-primary">
+ <div class="container">
+ <a href="index.html" class="navbar-brand">Apache Geronimo Arthur</a>
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
+ aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+ <div class="collapse navbar-collapse justify-content-end" id="navbarResponsive">
+ <ul class="navbar-nav">
+ <li class="nav-item">
+ <a class="nav-link" href="https://www.apache.org/">Apache</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="documentation.html">Documentation</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="community.html">Community</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="downloads.html">Downloads</a>
+ </li>
+ <li class="nav-item dropdown">
+ <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" id="asf-links">ASF <span
+ class="caret"></span></a>
+ <div class="dropdown-menu" aria-labelledby="asf-links">
+ <a class="dropdown-item" href="https://www.apache.org/licenses/">License</a>
+ <a class="dropdown-item"
+ href="https://www.apache.org/foundation/sponsorship.html">Sponsorship</a>
+ <a class="dropdown-item" href="https://www.apache.org/foundation/thanks.html">Thanks</a>
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item" href="https://www.apache.org/security/">Security</a>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </div>
+</div>
+
+<div class="container page-header">
\ No newline at end of file
diff --git a/documentation/src/test/java/org/apache/geronimo/arthur/documentation/lang/PathPredicatesTest.java b/documentation/src/test/java/org/apache/geronimo/arthur/documentation/lang/PathPredicatesTest.java
new file mode 100644
index 0000000..3782857
--- /dev/null
+++ b/documentation/src/test/java/org/apache/geronimo/arthur/documentation/lang/PathPredicatesTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.documentation.lang;
+
+import static java.util.Collections.singletonList;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+
+class PathPredicatesTest {
+ @Test
+ void nullValue() {
+ final Predicate<Path> adoc = new PathPredicates().createFilter(null);
+ assertTrue(adoc.test(Paths.get("foo.adoc")));
+ assertTrue(adoc.test(Paths.get(".foo.adoc")));
+ assertTrue(adoc.test(Paths.get(".git")));
+ assertTrue(adoc.test(Paths.get("whatever")));
+ }
+
+ @Test
+ void simple() {
+ final Predicate<Path> adoc = new PathPredicates().createFilter(singletonList(".+\\.adoc"));
+ assertTrue(adoc.test(Paths.get("foo.adoc")));
+ assertTrue(adoc.test(Paths.get(".foo.adoc")));
+ assertFalse(adoc.test(Paths.get(".git")));
+ assertFalse(adoc.test(Paths.get("whatever")));
+
+ final Predicate<Path> dotted = new PathPredicates().createFilter(singletonList("^\\..+"));
+ assertFalse(dotted.test(Paths.get("foo.adoc")));
+ assertTrue(dotted.test(Paths.get(".foo.adoc")));
+ assertTrue(dotted.test(Paths.get(".git")));
+ assertFalse(dotted.test(Paths.get("whatever")));
+ }
+
+ @Test
+ void includeExclude() {
+ final Predicate<Path> filter = new PathPredicates().createFilter(singletonList(".+\\.adoc"), singletonList("^\\..+"));
+ assertTrue(filter.test(Paths.get("foo.adoc")));
+ assertFalse(filter.test(Paths.get(".foo.adoc")));
+ assertFalse(filter.test(Paths.get(".git")));
+ assertFalse(filter.test(Paths.get("whatever")));
+ }
+
+ @Test
+ void includeExcludeNullInclude() {
+ final Predicate<Path> filter = new PathPredicates().createFilter(null, singletonList("^\\..+"));
+ assertTrue(filter.test(Paths.get("foo.adoc")));
+ assertFalse(filter.test(Paths.get(".foo.adoc")));
+ assertFalse(filter.test(Paths.get(".git")));
+ assertTrue(filter.test(Paths.get("whatever")));
+ }
+}
diff --git a/pom.xml b/pom.xml
index 041fe2e..d510eca 100644
--- a/pom.xml
+++ b/pom.xml
@@ -50,6 +50,7 @@
<module>arthur-impl</module>
<module>arthur-maven-plugin</module>
<module>arthur-spi</module>
+ <module>documentation</module>
</modules>
<dependencies>
@@ -121,6 +122,8 @@
<excludes>
<exclude>**/*/MANIFEST.MF</exclude>
<exclude>**/META-INF/services/*</exclude>
+ <exclude>**/crest-commands.txt</exclude>
+ <exclude>**/*.html</exclude>
</excludes>
</configuration>
<executions>