Repository: asciidoctor/asciidoctor-maven-plugin
Pull request #977 (2024-11-03 18:07:18)

(main) Use Maven Eclipse Sisu to inject components
Thank you for opening a pull request and contributing to asciidoctor-maven-plugin!

**What kind of change does this PR introduce?** (check at least one)
<!-- Update "[ ]" to "[x]" to check a box -->

- [ ] Bugfix
- [ ] Feature
- [ ] Documentation
- [x] Refactor
- [ ] Build improvement
- [ ] Other (please describe)


**What is the goal of this pull request?**

Use Eclipse Sisu for dependency injection to better organize code. We have been using manual instance creation and that's limiting how modular we can make the code (TLDR; it's easier to create static method helper classes or private methods than cohesive components).

This has already allowed some simple refactors (more to come), and reduce usage of reflection for testing.

Notes:
* For now we pass `Log` to methods for simplicity. With Maven 4 that will be removed in favor of normal slf4j.

**Are there any alternative ways to implement this?**

Still experimenting.

**Are there any implications of this pull request? Anything a user must know?**

No, changes are transparent to users.

**Is it related to an existing issue?**
<!-- If Yes, please add a line of the form: `Fixes #Issue` --> 
- [x] Yes
- [ ] No

 #945



Patch:
diff --git a/.github/workflows/build-maven-4.yaml b/.github/workflows/build-maven-4.yaml
index 69a40cb..202cbb0 100644
--- a/.github/workflows/build-maven-4.yaml
+++ b/.github/workflows/build-maven-4.yaml
@@ -39,5 +39,5 @@ jobs:
       - name: Maven version
         run: mvn -version
       - name: Build & Test
-        run: mvn -B -Prun-its clean verify -D"maven.version=${{ matrix.maven }}" -D"plexus-utils.version=4.0.2"
+        run: mvn -B -Prun-its -Pmaven4 clean verify
 
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 12f3291..1b610e6 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -13,6 +13,11 @@ For a detailed view of what has changed, refer to the {uri-repo}/commits/main[co
 
 == Unreleased
 
+Improvements (for all modules)::
+
+  * Use Maven Eclipse Sisu to inject components (allowed refactors to increase code re-use) (#945)
+  * Centralize Maven4 configurations in a profile in the parent (#945)
+
 Bug Fixes (Site Modules)::
 
   * Fix resolution of baseDir for Doxia modules (#968)
diff --git a/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParser.java b/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParser.java
index e334ad3..b3faf25 100644
--- a/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParser.java
+++ b/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParser.java
@@ -1,7 +1,6 @@
 package org.asciidoctor.maven.site;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 import java.io.File;
 import java.io.IOException;
 import java.io.Reader;
@@ -12,14 +11,8 @@ import org.apache.maven.doxia.parser.Parser;
 import org.apache.maven.doxia.sink.Sink;
 import org.apache.maven.project.MavenProject;
 import org.asciidoctor.Asciidoctor;
-import org.asciidoctor.Attributes;
-import org.asciidoctor.AttributesBuilder;
-import org.asciidoctor.Options;
-import org.asciidoctor.OptionsBuilder;
-import org.asciidoctor.SafeMode;
 import org.asciidoctor.maven.commons.StringUtils;
 import org.asciidoctor.maven.log.LogHandler;
-import org.asciidoctor.maven.log.LogRecordFormatter;
 import org.asciidoctor.maven.log.LogRecordsProcessors;
 import org.asciidoctor.maven.log.MemoryLogHandler;
 import org.asciidoctor.maven.site.SiteConverterDecorator.Result;
@@ -29,8 +22,6 @@ import org.codehaus.plexus.util.xml.Xpp3Dom;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static org.asciidoctor.maven.site.SiteBaseDirResolver.resolveBaseDir;
-
 /**
  * This class is used by <a href="https://maven.apache.org/doxia/overview.html">the Doxia framework</a>
  * to handle the actual parsing of the AsciiDoc input files into HTML to be consumed/wrapped
@@ -43,15 +34,28 @@ import static org.asciidoctor.maven.site.SiteBaseDirResolver.resolveBaseDir;
 @Component(role = Parser.class, hint = AsciidoctorConverterDoxiaParser.ROLE_HINT)
 public class AsciidoctorConverterDoxiaParser extends AbstractTextParser {
 
-    private static final Logger logger = LoggerFactory.getLogger(AsciidoctorConverterDoxiaParser.class);
-
-    @Inject
-    protected Provider<MavenProject> mavenProjectProvider;
-
     /**
      * The role hint for the {@link AsciidoctorConverterDoxiaParser} Plexus component.
      */
-    public static final String ROLE_HINT = "asciidoc";
+    static final String ROLE_HINT = "asciidoc";
+
+    private static final Logger logger = LoggerFactory.getLogger(AsciidoctorConverterDoxiaParser.class);
+
+    private final MavenProject mavenProject;
+    private final SiteConversionConfigurationParser siteConfigParser;
+    private final LogHandlerFactory logHandlerFactory;
+    private final SiteConverterDecorator siteConverter;
+
+    @Inject
+    public AsciidoctorConverterDoxiaParser(MavenProject mavenProject,
+                                           SiteConversionConfigurationParser siteConfigParser,
+                                           LogHandlerFactory logHandlerFactory,
+                                           SiteConverterDecorator siteConverter) {
+        this.mavenProject = mavenProject;
+        this.siteConfigParser = siteConfigParser;
+        this.logHandlerFactory = logHandlerFactory;
+        this.siteConverter = siteConverter;
+    }
 
     /**
      * {@inheritDoc}
@@ -68,9 +72,9 @@ public class AsciidoctorConverterDoxiaParser extends AbstractTextParser {
             return;
         }
 
-        final MavenProject project = mavenProjectProvider.get();
-        final Xpp3Dom siteConfig = getSiteConfig(project);
-        final File siteDirectory = resolveBaseDir(project.getBasedir(), siteConfig);
+        final SiteConversionConfiguration conversionConfig = siteConfigParser.processAsciiDocConfig(mavenProject, ROLE_HINT);
+        final Xpp3Dom asciidocConfig = conversionConfig.getAsciidocConfig();
+        final File siteDirectory = conversionConfig.getSiteBaseDir();
 
         // Doxia handles a single instance of this class and invokes it multiple times.
         // We need to ensure certain elements are initialized only once to avoid errors.
@@ -78,17 +82,14 @@ public class AsciidoctorConverterDoxiaParser extends AbstractTextParser {
         // And overriding init and other methods form parent classes does not work.
         final Asciidoctor asciidoctor = Asciidoctor.Factory.create();
 
-        SiteConversionConfiguration conversionConfig = new SiteConversionConfigurationParser(project)
-            .processAsciiDocConfig(siteConfig, defaultOptions(siteDirectory), defaultAttributes());
         for (String require : conversionConfig.getRequires()) {
             requireLibrary(asciidoctor, require);
         }
 
-        final LogHandler logHandler = getLogHandlerConfig(siteConfig);
-        final MemoryLogHandler memoryLogHandler = asciidoctorLoggingSetup(asciidoctor, siteDirectory);
+        final LogHandler logHandler = logHandlerFactory.getConfiguration(asciidocConfig);
+        final MemoryLogHandler memoryLogHandler = logHandlerFactory.create(asciidoctor, siteDirectory, logger);
 
-        final SiteConverterDecorator siteConverter = new SiteConverterDecorator(asciidoctor);
-        final Result headerMetadata = siteConverter.process(source, conversionConfig.getOptions());
+        final Result headerMetadata = siteConverter.process(asciidoctor, source, conversionConfig.getOptions());
 
         try {
             // process log messages according to mojo configuration
@@ -97,7 +98,7 @@ public class AsciidoctorConverterDoxiaParser extends AbstractTextParser {
                 if (logHandler.getOutputToConsole() && StringUtils.isNotBlank(reference)) {
                     memoryLogHandler.processAll();
                 }
-                new LogRecordsProcessors(logHandler, siteDirectory, errorMessage -> logger.error(errorMessage))
+                new LogRecordsProcessors(logHandler, siteDirectory, logger::error)
                     .processLogRecords(memoryLogHandler);
             }
         } catch (Exception exception) {
@@ -110,45 +111,6 @@ public class AsciidoctorConverterDoxiaParser extends AbstractTextParser {
         sink.rawText(headerMetadata.getHtml());
     }
 
-    private MemoryLogHandler asciidoctorLoggingSetup(Asciidoctor asciidoctor, File siteDirectory) {
-
-        final MemoryLogHandler memoryLogHandler = new MemoryLogHandler(false,
-            logRecord -> logger.info(LogRecordFormatter.format(logRecord, siteDirectory)));
-        asciidoctor.registerLogHandler(memoryLogHandler);
-        // disable default console output of AsciidoctorJ
-        java.util.logging.Logger.getLogger("asciidoctor").setUseParentHandlers(false);
-        return memoryLogHandler;
-    }
-
-    private LogHandler getLogHandlerConfig(Xpp3Dom siteConfig) {
-        Xpp3Dom asciidoc = siteConfig == null ? null : siteConfig.getChild("asciidoc");
-        return new SiteLogHandlerDeserializer().deserialize(asciidoc);
-    }
-
-    protected Xpp3Dom getSiteConfig(MavenProject project) {
-        return project.getGoalConfiguration("org.apache.maven.plugins", "maven-site-plugin", "site", "site");
-    }
-
-
-    // The possible baseDir based on configuration are:
-    //
-    // with nothing                : src/site + /asciidoc
-    // with locale                 : src/site + {locale} +  /asciidoc
-    // with siteDirectory          : {siteDirectory} + /asciidoc
-    // with siteDirectory + locale : {siteDirectory} + {locale} + /asciidoc
-    protected OptionsBuilder defaultOptions(File siteDirectory) {
-        return Options.builder()
-            .backend("xhtml")
-            .safe(SafeMode.UNSAFE)
-            .baseDir(new File(siteDirectory, ROLE_HINT).getAbsoluteFile());
-    }
-
-    protected AttributesBuilder defaultAttributes() {
-        return Attributes.builder()
-            .attribute("idprefix", "@")
-            .attribute("showtitle", "@");
-    }
-
     private void requireLibrary(Asciidoctor asciidoctor, String require) {
         if (!(require = require.trim()).isEmpty()) {
             try {
@@ -158,4 +120,5 @@ public class AsciidoctorConverterDoxiaParser extends AbstractTextParser {
             }
         }
     }
+
 }
diff --git a/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParserModule.java b/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParserModule.java
index cb39ac9..f352ce4 100644
--- a/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParserModule.java
+++ b/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParserModule.java
@@ -11,6 +11,7 @@ import org.codehaus.plexus.component.annotations.Component;
  * <a href="https://maven.apache.org/doxia/references/">Doxia provided modules</a>.
  *
  * @author jdlee
+ * @since 0.1.2.1
  */
 @Component(role = ParserModule.class, hint = AsciidoctorConverterDoxiaParser.ROLE_HINT)
 public class AsciidoctorConverterDoxiaParserModule extends AbstractParserModule {
diff --git a/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/SiteConverterDecorator.java b/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/SiteConverterDecorator.java
index 8fecdaa..1beaf0c 100644
--- a/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/SiteConverterDecorator.java
+++ b/asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/SiteConverterDecorator.java
@@ -1,5 +1,6 @@
 package org.asciidoctor.maven.site;
 
+import javax.inject.Singleton;
 import java.util.Map;
 
 import org.asciidoctor.Asciidoctor;
@@ -10,16 +11,14 @@ import org.asciidoctor.ast.Document;
 /**
  * Asciidoctor conversion wrapper for maven-site integration.
  * In addition to conversion, handles header metadata extraction.
+ *
+ * @author abelsromero
+ * @since 3.0.0
  */
+@Singleton
 class SiteConverterDecorator {
 
-    private final Asciidoctor asciidoctor;
-
-    SiteConverterDecorator(Asciidoctor asciidoctor) {
-        this.asciidoctor = asciidoctor;
-    }
-
-    Result process(String content, Options options) {
+    Result process(Asciidoctor asciidoctor, String content, Options options) {
         final Document document = asciidoctor.load(content, headerProcessingMetadata(options));
         final HeaderMetadata headerMetadata = HeaderMetadata.from(document);
 
@@ -61,5 +60,4 @@ class SiteConverterDecorator {
         }
     }
 
-
 }
diff --git a/asciidoctor-maven-commons/pom.xml b/asciidoctor-maven-commons/pom.xml
index d41012c..acc7f0f 100644
--- a/asciidoctor-maven-commons/pom.xml
+++ b/asciidoctor-maven-commons/pom.xml
@@ -21,10 +21,6 @@
     </description>
     <url>https://github.com/asciidoctor/asciidoctor-maven-plugin</url>
 
-    <properties>
-        <plexus-utils.version>3.5.1</plexus-utils.version>
-    </properties>
-
     <dependencies>
         <dependency>
             <groupId>org.asciidoctor</groupId>
@@ -37,6 +33,7 @@
             <version>${maven.version}</version>
             <scope>provided</scope>
         </dependency>
+        <!-- Required: see https://issues.apache.org/jira/browse/MNG-6965 -->
         <dependency>
             <groupId>org.codehaus.plexus</groupId>
             <artifactId>plexus-utils</artifactId>
diff --git a/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/LogHandlerFactory.java b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/LogHandlerFactory.java
new file mode 100644
index 0000000..dbca602
--- /dev/null
+++ b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/LogHandlerFactory.java
@@ -0,0 +1,34 @@
+package org.asciidoctor.maven.site;
+
+import java.io.File;
+
+import org.asciidoctor.Asciidoctor;
+import org.asciidoctor.maven.log.LogHandler;
+import org.asciidoctor.maven.log.LogRecordFormatter;
+import org.asciidoctor.maven.log.MemoryLogHandler;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.slf4j.Logger;
+
+/**
+ * Factory to parse and create {@link MemoryLogHandler} in order to handle
+ * issues during processing.
+ *
+ * @author abelsromero
+ * @since 3.1.1
+ */
+public class LogHandlerFactory {
+
+    public LogHandler getConfiguration(Xpp3Dom asciidocConfig) {
+        return new SiteLogHandlerDeserializer().deserialize(asciidocConfig);
+    }
+
+    public MemoryLogHandler create(Asciidoctor asciidoctor, File siteDirectory, Logger logger) {
+
+        final MemoryLogHandler memoryLogHandler = new MemoryLogHandler(false,
+            logRecord -> logger.info(LogRecordFormatter.format(logRecord, siteDirectory)));
+        asciidoctor.registerLogHandler(memoryLogHandler);
+        // disable default console output of AsciidoctorJ
+        java.util.logging.Logger.getLogger("asciidoctor").setUseParentHandlers(false);
+        return memoryLogHandler;
+    }
+}
diff --git a/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteBaseDirResolver.java b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteBaseDirResolver.java
index 124ea91..23073e6 100644
--- a/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteBaseDirResolver.java
+++ b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteBaseDirResolver.java
@@ -1,5 +1,6 @@
 package org.asciidoctor.maven.site;
 
+import javax.inject.Singleton;
 import java.io.File;
 import java.nio.file.Path;
 
@@ -15,11 +16,14 @@ import org.slf4j.LoggerFactory;
  * @author abelsromero
  * @since 3.1.1
  */
+@Singleton
 public class SiteBaseDirResolver {
 
     private static final Logger logger = LoggerFactory.getLogger(SiteBaseDirResolver.class);
+    private static final String DEFAULT_SOURCE_LOCATION = "src/site";
 
-    public static File resolveBaseDir(File mavenBaseDir, Xpp3Dom siteConfig) {
+
+    File resolveBaseDir(File mavenBaseDir, Xpp3Dom siteConfig) {
         final String siteDirectory = resolveSiteDirectory(siteConfig);
         final String locale = resolveLocale(siteConfig);
 
@@ -32,9 +36,9 @@ public class SiteBaseDirResolver {
             return normalize(path, siteDirectory);
 
         if (locale != null)
-            return normalize(path, "src/site", locale);
+            return normalize(path, DEFAULT_SOURCE_LOCATION, locale);
 
-        return normalize(path, "src/site");
+        return normalize(path, DEFAULT_SOURCE_LOCATION);
     }
 
     private static String resolveSiteDirectory(Xpp3Dom siteConfig) {
diff --git a/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteConversionConfiguration.java b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteConversionConfiguration.java
index 98e3292..642f590 100644
--- a/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteConversionConfiguration.java
+++ b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteConversionConfiguration.java
@@ -1,19 +1,40 @@
 package org.asciidoctor.maven.site;
 
+import java.io.File;
 import java.util.List;
 
 import org.asciidoctor.Options;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
 
-public class SiteConversionConfiguration {
+/**
+ * @author abelsromero
+ * @since 3.1.1
+ */
+public final class SiteConversionConfiguration {
 
+    private final Xpp3Dom asciidocConfig;
+    private final File siteBaseDir;
     private final Options options;
     private final List<String> requires;
 
-    SiteConversionConfiguration(Options options, List<String> requires) {
+    SiteConversionConfiguration(Xpp3Dom asciidocConfig,
+                                File siteBaseDir,
+                                Options options,
+                                List<String> requires) {
+        this.asciidocConfig = asciidocConfig;
+        this.siteBaseDir = siteBaseDir;
         this.options = options;
         this.requires = requires;
     }
 
+    public File getSiteBaseDir() {
+        return siteBaseDir;
+    }
+
+    public Xpp3Dom getAsciidocConfig() {
+        return asciidocConfig;
+    }
+
     public Options getOptions() {
         return options;
     }
diff --git a/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteConversionConfigurationParser.java b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteConversionConfigurationParser.java
index 840184e..d563e15 100644
--- a/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteConversionConfigurationParser.java
+++ b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/site/SiteConversionConfigurationParser.java
@@ -1,49 +1,62 @@
 package org.asciidoctor.maven.site;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
 import org.apache.maven.project.MavenProject;
 import org.asciidoctor.Attributes;
 import org.asciidoctor.AttributesBuilder;
 import org.asciidoctor.Options;
 import org.asciidoctor.OptionsBuilder;
+import org.asciidoctor.SafeMode;
 import org.asciidoctor.maven.commons.AsciidoctorHelper;
 import org.asciidoctor.maven.commons.StringUtils;
 import org.codehaus.plexus.util.xml.Xpp3Dom;
 
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
 import static org.asciidoctor.maven.commons.StringUtils.isNotBlank;
 
+/**
+ * Extract Asciidoctor required configurations from Maven Site Plugin
+ * configuration in POM file.
+ *
+ * @author mojavelinux
+ * @author abelsromero
+ * @since 2.0.0
+ */
+@Singleton
 public class SiteConversionConfigurationParser {
 
-    private final MavenProject project;
+    private final SiteBaseDirResolver siteBaseDirResolver;
 
-    public SiteConversionConfigurationParser(MavenProject project) {
-        this.project = project;
+    @Inject
+    public SiteConversionConfigurationParser(SiteBaseDirResolver siteBaseDirResolver) {
+        this.siteBaseDirResolver = siteBaseDirResolver;
     }
 
-    public SiteConversionConfiguration processAsciiDocConfig(Xpp3Dom siteConfig,
-                                                             OptionsBuilder presetOptions,
-                                                             AttributesBuilder presetAttributes) {
-
-        AsciidoctorHelper.addProperties(project.getProperties(), presetAttributes);
+    public SiteConversionConfiguration processAsciiDocConfig(MavenProject mavenProject, String roleHint) {
 
+        final AttributesBuilder presetAttributes = defaultAttributes();
+        AsciidoctorHelper.addProperties(mavenProject.getProperties(), presetAttributes);
         final Attributes attributes = presetAttributes.build();
 
-        if (siteConfig == null) {
-            final Options options = presetOptions.attributes(attributes).build();
-            return new SiteConversionConfiguration(options, Collections.emptyList());
-        }
+        final File siteDir = siteBaseDirResolver.resolveBaseDir(mavenProject.getBasedir(), getSiteConfig(mavenProject));
+        final OptionsBuilder presetOptions = defaultOptions(siteDir, roleHint);
+
+        final Xpp3Dom asciidocConfig = Optional.ofNullable(getSiteConfig(mavenProject))
+            .map(node -> node.getChild("asciidoc"))
+            .orElse(null);
 
-        final Xpp3Dom asciidocConfig = siteConfig.getChild("asciidoc");
         if (asciidocConfig == null) {
             final Options options = presetOptions.attributes(attributes).build();
-            return new SiteConversionConfiguration(options, Collections.emptyList());
+            return new SiteConversionConfiguration(null, siteDir, options, Collections.emptyList());
         }
 
         final List<String> gemsToRequire = new ArrayList<>();
@@ -57,9 +70,9 @@ public class SiteConversionConfigurationParser {
                         if (requireNode.getValue().contains(",")) {
                             // <requires>time, base64</requires>
                             Stream.of(requireNode.getValue().split(","))
-                                    .filter(StringUtils::isNotBlank)
-                                    .map(String::trim)
-                                    .forEach(value -> gemsToRequire.add(value));
+                                .filter(StringUtils::isNotBlank)
+                                .map(String::trim)
+                                .forEach(value -> gemsToRequire.add(value));
                         } else {
                             // <requires>
                             //     <require>time</require>
@@ -76,23 +89,46 @@ public class SiteConversionConfigurationParser {
                 }
             } else if ("templateDirs".equals(optName) || "template_dirs".equals(optName)) {
                 List<File> dirs = Arrays.stream(asciidocOpt.getChildren("dir"))
-                        .filter(node -> isNotBlank(node.getValue()))
-                        .map(node -> resolveProjectDir(project, node.getValue()))
-                        .collect(Collectors.toList());
+                    .filter(node -> isNotBlank(node.getValue()))
+                    .map(node -> resolveProjectDir(mavenProject, node.getValue()))
+                    .collect(Collectors.toList());
                 presetOptions.templateDirs(dirs.toArray(new File[dirs.size()]));
             } else if ("baseDir".equals(optName)) {
-                presetOptions.baseDir(resolveProjectDir(project, asciidocOpt.getValue()));
+                presetOptions.baseDir(resolveProjectDir(mavenProject, asciidocOpt.getValue()));
             } else {
                 presetOptions.option(optName.replaceAll("(?<!_)([A-Z])", "_$1").toLowerCase(), asciidocOpt.getValue());
             }
         }
 
         final Options options = presetOptions.attributes(attributes).build();
-        return new SiteConversionConfiguration(options, gemsToRequire);
+        return new SiteConversionConfiguration(asciidocConfig, siteDir, options, gemsToRequire);
     }
 
-    private File resolveProjectDir(MavenProject project, String path) {
+    private Xpp3Dom getSiteConfig(MavenProject mavenProject) {
+        return mavenProject.getGoalConfiguration("org.apache.maven.plugins", "maven-site-plugin", "site", "site");
+    }
+
+    private File resolveProjectDir(MavenProject mavenProject, String path) {
         final File filePath = new File(path);
-        return !filePath.isAbsolute() ? new File(project.getBasedir(), filePath.toString()).getAbsoluteFile() : filePath;
+        return !filePath.isAbsolute() ? new File(mavenProject.getBasedir(), filePath.toString()).getAbsoluteFile() : filePath;
+    }
+
+    // The possible baseDir based on configuration are:
+    //
+    // with nothing                : src/site + /asciidoc
+    // with locale                 : src/site + {locale} +  /asciidoc
+    // with siteDirectory          : {siteDirectory} + /asciidoc
+    // with siteDirectory + locale : {siteDirectory} + {locale} + /asciidoc
+    private OptionsBuilder defaultOptions(File siteDirectory, String roleHint) {
+        return Options.builder()
+            .backend("xhtml")
+            .safe(SafeMode.UNSAFE)
+            .baseDir(new File(siteDirectory, roleHint).getAbsoluteFile());
+    }
+
+    private AttributesBuilder defaultAttributes() {
+        return Attributes.builder()
+            .attribute("idprefix", "@")
+            .attribute("showtitle", "@");
     }
 }
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorHttpMojo.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorHttpMojo.java
index c5d3130..653e442 100644
--- a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorHttpMojo.java
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorHttpMojo.java
@@ -1,10 +1,14 @@
 package org.asciidoctor.maven;
 
+import javax.inject.Inject;
+
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.asciidoctor.maven.http.AsciidoctorHttpServer;
+import org.asciidoctor.maven.process.ResourcesProcessor;
+import org.asciidoctor.maven.process.SourceDocumentFinder;
 
 @Mojo(name = "http")
 public class AsciidoctorHttpMojo extends AsciidoctorRefreshMojo {
@@ -17,6 +21,10 @@ public class AsciidoctorHttpMojo extends AsciidoctorRefreshMojo {
     @Parameter(property = PREFIX + "home", defaultValue = "index")
     protected String home;
 
+    @Inject
+    public AsciidoctorHttpMojo(AsciidoctorJFactory asciidoctorJFactory, AsciidoctorOptionsFactory asciidoctorOptionsFactory, SourceDocumentFinder finder, ResourcesProcessor defaultResourcesProcessor) {
+        super(asciidoctorJFactory, asciidoctorOptionsFactory, finder, defaultResourcesProcessor);
+    }
 
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorJFactory.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorJFactory.java
new file mode 100644
index 0000000..1edc435
--- /dev/null
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorJFactory.java
@@ -0,0 +1,61 @@
+package org.asciidoctor.maven;
+
+import javax.inject.Singleton;
+import java.io.File;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.logging.Log;
+import org.asciidoctor.Asciidoctor;
+import org.asciidoctor.jruby.AsciidoctorJRuby;
+import org.asciidoctor.jruby.internal.JRubyRuntimeContext;
+import org.jruby.Ruby;
+
+/**
+ * Creates an {@link Asciidoctor} instance taking into consideration the project's
+ * GEMs configuration.
+ *
+ * @author abelsromero
+ * @since 3.1.1
+ */
+@Singleton
+public class AsciidoctorJFactory {
+
+    Asciidoctor create(String gemPath, Log log) throws MojoExecutionException {
+        Asciidoctor asciidoctor;
+        if (gemPath == null) {
+            asciidoctor = AsciidoctorJRuby.Factory.create();
+        } else {
+            // Replace Windows path separator to avoid paths with mixed \ and /.
+            // This happens for instance when setting: <gemPath>${project.build.directory}/gems-provided</gemPath>
+            // because the project's path is converted to string.
+            String normalizedGemPath = (File.separatorChar == '\\') ? gemPath.replaceAll("\\\\", "/") : gemPath;
+            asciidoctor = AsciidoctorJRuby.Factory.create(normalizedGemPath);
+        }
+
+        Ruby rubyInstance = null;
+        try {
+            rubyInstance = (Ruby) JRubyRuntimeContext.class.getMethod("get")
+                .invoke(null);
+        } catch (NoSuchMethodException e) {
+            if (rubyInstance == null) {
+                try {
+                    rubyInstance = (Ruby) JRubyRuntimeContext.class.getMethod("get", Asciidoctor.class).invoke(null, asciidoctor);
+                } catch (Exception e1) {
+                    throw new MojoExecutionException("Failed to invoke get(AsciiDoctor) for JRubyRuntimeContext", e1);
+                }
+
+            }
+        } catch (Exception e) {
+            throw new MojoExecutionException("Failed to invoke get for JRubyRuntimeContext", e);
+        }
+
+        String gemHome = rubyInstance.evalScriptlet("ENV['GEM_HOME']").toString();
+        String gemHomeExpected = (gemPath == null || "".equals(gemPath)) ? "" : gemPath.split(java.io.File.pathSeparator)[0];
+
+        if (!"".equals(gemHome) && !gemHomeExpected.equals(gemHome)) {
+            log.warn("Using inherited external environment to resolve gems (" + gemHome + "), i.e. build is platform dependent!");
+        }
+
+        return asciidoctor;
+    }
+}
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java
index a51bc12..ef48d04 100644
--- a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java
@@ -5,7 +5,6 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -22,14 +21,8 @@ import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProject;
 import org.asciidoctor.Asciidoctor;
-import org.asciidoctor.Attributes;
-import org.asciidoctor.AttributesBuilder;
 import org.asciidoctor.Options;
 import org.asciidoctor.OptionsBuilder;
-import org.asciidoctor.SafeMode;
-import org.asciidoctor.jruby.AsciidoctorJRuby;
-import org.asciidoctor.jruby.internal.JRubyRuntimeContext;
-import org.asciidoctor.maven.commons.AsciidoctorHelper;
 import org.asciidoctor.maven.extensions.AsciidoctorJExtensionRegistry;
 import org.asciidoctor.maven.extensions.ExtensionConfiguration;
 import org.asciidoctor.maven.extensions.ExtensionRegistry;
@@ -38,14 +31,10 @@ import org.asciidoctor.maven.log.LogRecordFormatter;
 import org.asciidoctor.maven.log.LogRecordsProcessors;
 import org.asciidoctor.maven.log.MemoryLogHandler;
 import org.asciidoctor.maven.model.Resource;
-import org.asciidoctor.maven.process.CopyResourcesProcessor;
 import org.asciidoctor.maven.process.ResourcesProcessor;
 import org.asciidoctor.maven.process.SourceDirectoryFinder;
 import org.asciidoctor.maven.process.SourceDocumentFinder;
-import org.jruby.Ruby;
 
-import static org.asciidoctor.maven.commons.StringUtils.isBlank;
-import static org.asciidoctor.maven.commons.StringUtils.isNotBlank;
 import static org.asciidoctor.maven.process.SourceDirectoryFinder.DEFAULT_SOURCE_DIR;
 
 
@@ -146,12 +135,25 @@ public class AsciidoctorMojo extends AbstractMojo {
     @Inject
     protected MavenProject project;
 
+    private final AsciidoctorJFactory asciidoctorJFactory;
+    private final AsciidoctorOptionsFactory asciidoctorOptionsFactory;
+    private final SourceDocumentFinder finder;
+    protected final ResourcesProcessor resourcesProcessor;
 
-    protected final ResourcesProcessor defaultResourcesProcessor = new CopyResourcesProcessor();
+    @Inject
+    public AsciidoctorMojo(AsciidoctorJFactory asciidoctorJFactory,
+                           AsciidoctorOptionsFactory asciidoctorOptionsFactory,
+                           SourceDocumentFinder finder,
+                           ResourcesProcessor resourcesProcessor) {
+        this.asciidoctorJFactory = asciidoctorJFactory;
+        this.asciidoctorOptionsFactory = asciidoctorOptionsFactory;
+        this.finder = finder;
+        this.resourcesProcessor = resourcesProcessor;
+    }
 
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
-        processAllSources(defaultResourcesProcessor);
+        processAllSources(resourcesProcessor);
     }
 
     /**
@@ -209,7 +211,8 @@ public class AsciidoctorMojo extends AbstractMojo {
             }
         }
 
-        final Asciidoctor asciidoctor = getAsciidoctorInstance(gemPath);
+        final Asciidoctor asciidoctor = asciidoctorJFactory.create(gemPath, getLog());
+
         if (enableVerbose) {
             asciidoctor.requireLibrary("enable_verbose.rb");
         }
@@ -224,8 +227,7 @@ public class AsciidoctorMojo extends AbstractMojo {
             }
         }
 
-        AttributesBuilder attributesBuilder = createAttributesBuilder(this, project);
-        OptionsBuilder optionsBuilder = createOptionsBuilder(this, attributesBuilder);
+        OptionsBuilder optionsBuilder = asciidoctorOptionsFactory.create(this, project, getLog());
 
         // Copy output resources
         final File sourceDir = sourceDirectoryCandidate.get();
@@ -234,7 +236,7 @@ public class AsciidoctorMojo extends AbstractMojo {
         // register LogHandler to capture asciidoctor messages
         final Boolean outputToConsole = logHandler.getOutputToConsole() == null ? Boolean.TRUE : logHandler.getOutputToConsole();
         final MemoryLogHandler memoryLogHandler = new MemoryLogHandler(outputToConsole,
-                logRecord -> getLog().info(LogRecordFormatter.format(logRecord, sourceDir)));
+            logRecord -> getLog().info(LogRecordFormatter.format(logRecord, sourceDir)));
         asciidoctor.registerLogHandler(memoryLogHandler);
         // disable default console output of AsciidoctorJ
         Logger.getLogger("asciidoctor").setUseParentHandlers(false);
@@ -257,7 +259,7 @@ public class AsciidoctorMojo extends AbstractMojo {
             try {
                 // process log messages according to mojo configuration
                 new LogRecordsProcessors(logHandler, sourceDir, errorMessage -> getLog().error(errorMessage))
-                        .processLogRecords(memoryLogHandler);
+                    .processLogRecords(memoryLogHandler);
             } catch (Exception exception) {
                 throw new MojoExecutionException(exception.getMessage());
             }
@@ -266,12 +268,12 @@ public class AsciidoctorMojo extends AbstractMojo {
 
     public Optional<File> findSourceDirectory(File initialSourceDirectory, File baseDir) {
         Optional<File> sourceDirCandidate = new SourceDirectoryFinder(initialSourceDirectory, baseDir,
-                candidate -> {
-                    String candidateName = candidate.toString();
-                    if (isRelativePath(candidateName)) candidateName = candidateName.substring(2);
-                    getLog().info("sourceDirectory " + candidateName + " does not exist");
-                })
-                .find();
+            candidate -> {
+                String candidateName = candidate.toString();
+                if (isRelativePath(candidateName)) candidateName = candidateName.substring(2);
+                getLog().info("sourceDirectory " + candidateName + " does not exist");
+            })
+            .find();
 
         return sourceDirCandidate;
     }
@@ -317,8 +319,8 @@ public class AsciidoctorMojo extends AbstractMojo {
                 // allow overriding the output file name
                 optionsBuilder.toFile(outputFile);
                 return outputFile.isAbsolute()
-                        ? new Destination(outputFile, true)
-                        : new Destination(new File(toDir, outputFile.getPath()), true);
+                    ? new Destination(outputFile, true)
+                    : new Destination(new File(toDir, outputFile.getPath()), true);
             } else {
                 return new Destination(new File(toDir, sourceFile.getName()), false);
             }
@@ -338,59 +340,14 @@ public class AsciidoctorMojo extends AbstractMojo {
         }
     }
 
-    protected Asciidoctor getAsciidoctorInstance(String gemPath) throws MojoExecutionException {
-        Asciidoctor asciidoctor;
-        if (gemPath == null) {
-            asciidoctor = AsciidoctorJRuby.Factory.create();
-        } else {
-            // Replace Windows path separator to avoid paths with mixed \ and /.
-            // This happens for instance when setting: <gemPath>${project.build.directory}/gems-provided</gemPath>
-            // because the project's path is converted to string.
-            String normalizedGemPath = (File.separatorChar == '\\') ? gemPath.replaceAll("\\\\", "/") : gemPath;
-            asciidoctor = AsciidoctorJRuby.Factory.create(normalizedGemPath);
-        }
-
-        Ruby rubyInstance = null;
-        try {
-            rubyInstance = (Ruby) JRubyRuntimeContext.class.getMethod("get")
-                    .invoke(null);
-        } catch (NoSuchMethodException e) {
-            if (rubyInstance == null) {
-                try {
-                    rubyInstance = (Ruby) JRubyRuntimeContext.class.getMethod(
-                            "get", Asciidoctor.class).invoke(null, asciidoctor);
-                } catch (Exception e1) {
-                    throw new MojoExecutionException(
-                            "Failed to invoke get(AsciiDoctor) for JRubyRuntimeContext",
-                            e1);
-                }
-
-            }
-        } catch (Exception e) {
-            throw new MojoExecutionException(
-                    "Failed to invoke get for JRubyRuntimeContext", e);
-        }
-
-        String gemHome = rubyInstance.evalScriptlet("ENV['GEM_HOME']").toString();
-        String gemHomeExpected = (gemPath == null || "".equals(gemPath)) ? "" : gemPath.split(java.io.File.pathSeparator)[0];
-
-        if (!"".equals(gemHome) && !gemHomeExpected.equals(gemHome)) {
-            getLog().warn("Using inherited external environment to resolve gems (" + gemHome + "), i.e. build is platform dependent!");
-        }
-
-        return asciidoctor;
-    }
-
     protected List<File> findSourceFiles(File sourceDirectory) {
         if (sourceDocumentName != null)
-            return Arrays.asList(new File(sourceDirectory, sourceDocumentName));
-
-        Path sourceDirectoryPath = sourceDirectory.toPath();
-        SourceDocumentFinder finder = new SourceDocumentFinder();
+            return List.of(new File(sourceDirectory, sourceDocumentName));
 
+        final Path sourceDirectoryPath = sourceDirectory.toPath();
         return sourceDocumentExtensions.isEmpty() ?
-                finder.find(sourceDirectoryPath) :
-                finder.find(sourceDirectoryPath, sourceDocumentExtensions);
+            finder.find(sourceDirectoryPath) :
+            finder.find(sourceDirectoryPath, sourceDocumentExtensions);
     }
 
     protected void convertFile(Asciidoctor asciidoctor, Options options, File f) {
@@ -409,73 +366,6 @@ public class AsciidoctorMojo extends AbstractMojo {
         return true;
     }
 
-    /**
-     * Creates an OptionsBuilder instance with the options defined in the configuration.
-     *
-     * @param configuration     AsciidoctorMojo containing conversion configuration.
-     * @param attributesBuilder If not null, Asciidoctor attributes to add to the OptionsBuilder created.
-     * @return initialized optionsBuilder.
-     */
-    protected OptionsBuilder createOptionsBuilder(AsciidoctorMojo configuration, AttributesBuilder attributesBuilder) {
-
-        final OptionsBuilder optionsBuilder = Options.builder()
-                .backend(configuration.getBackend())
-                .safe(SafeMode.UNSAFE)
-                .standalone(configuration.standalone)
-                .mkDirs(true);
-
-        if (!isBlank(configuration.getEruby()))
-            optionsBuilder.eruby(configuration.getEruby());
-
-        if (configuration.isSourcemap())
-            optionsBuilder.option(Options.SOURCEMAP, true);
-
-        if (configuration.isCatalogAssets())
-            optionsBuilder.option(Options.CATALOG_ASSETS, true);
-
-        if (!configuration.isTemplateCache())
-            optionsBuilder.option(Options.TEMPLATE_CACHE, false);
-
-        if (configuration.getDoctype() != null)
-            optionsBuilder.docType(doctype);
-
-        if (configuration.getTemplateEngine() != null)
-            optionsBuilder.templateEngine(templateEngine);
-
-        if (!configuration.getTemplateDirs().isEmpty())
-            optionsBuilder.templateDirs(templateDirs.toArray(new File[]{}));
-
-        optionsBuilder.attributes(attributesBuilder.build());
-        return optionsBuilder;
-    }
-
-    /**
-     * Creates an AttributesBuilder instance with the attributes defined in the configuration.
-     *
-     * @param configuration AsciidoctorMojo containing conversion configuration.
-     * @param mavenProject  Current {@link MavenProject} instance.
-     * @return initialized attributesBuilder.
-     */
-    protected AttributesBuilder createAttributesBuilder(AsciidoctorMojo configuration, MavenProject mavenProject) {
-
-        final AttributesBuilder attributesBuilder = Attributes.builder();
-
-        if (configuration.isEmbedAssets()) {
-            attributesBuilder.linkCss(false);
-            attributesBuilder.dataUri(true);
-        }
-
-        AsciidoctorHelper.addProperties(mavenProject.getProperties(), attributesBuilder);
-        AsciidoctorHelper.addAttributes(configuration.getAttributes(), attributesBuilder);
-
-        if (isNotBlank(configuration.getAttributesChain())) {
-            getLog().info("Attributes: " + attributesChain);
-            attributesBuilder.arguments(attributesChain);
-        }
-
-        return attributesBuilder;
-    }
-
     public File getSourceDirectory() {
         return sourceDirectory;
     }
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorOptionsFactory.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorOptionsFactory.java
new file mode 100644
index 0000000..c6a7138
--- /dev/null
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorOptionsFactory.java
@@ -0,0 +1,98 @@
+package org.asciidoctor.maven;
+
+import javax.inject.Singleton;
+import java.io.File;
+
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.asciidoctor.Attributes;
+import org.asciidoctor.AttributesBuilder;
+import org.asciidoctor.Options;
+import org.asciidoctor.OptionsBuilder;
+import org.asciidoctor.SafeMode;
+import org.asciidoctor.maven.commons.AsciidoctorHelper;
+
+import static org.asciidoctor.maven.commons.StringUtils.isBlank;
+import static org.asciidoctor.maven.commons.StringUtils.isNotBlank;
+
+/**
+ * Creates an {@link OptionsBuilder} instance taking into consideration the project's
+ * configurations to be used or further customized.
+ * The instance also contains initialized {@link Attributes}.
+ *
+ * @author abelsromero
+ * @since 3.1.1
+ */
+@Singleton
+public class AsciidoctorOptionsFactory {
+
+    /**
+     * Creates an AttributesBuilder instance with the attributes defined in the configuration.
+     *
+     * @param configuration AsciidoctorMojo containing conversion configuration.
+     * @param mavenProject  Current {@link MavenProject} instance.
+     * @param log           The mojo's {@link Log} reference.
+     * @return initialized {@link Attributes}.
+     */
+    private Attributes createAttributes(AsciidoctorMojo configuration, MavenProject mavenProject, Log log) {
+
+        final AttributesBuilder attributesBuilder = Attributes.builder();
+
+        if (configuration.isEmbedAssets()) {
+            attributesBuilder.linkCss(false);
+            attributesBuilder.dataUri(true);
+        }
+
+        AsciidoctorHelper.addProperties(mavenProject.getProperties(), attributesBuilder);
+        AsciidoctorHelper.addAttributes(configuration.getAttributes(), attributesBuilder);
+
+        if (isNotBlank(configuration.getAttributesChain())) {
+            log.info("Attributes: " + configuration.getAttributesChain());
+            attributesBuilder.arguments(configuration.getAttributesChain());
+        }
+
+        return attributesBuilder.build();
+    }
+
+    /**
+     * Creates an OptionsBuilder instance with the options defined in the configuration.
+     *
+     * @param configuration AsciidoctorMojo containing conversion configuration.
+     * @param mavenProject  Current {@link MavenProject} instance.
+     * @param log           The mojo's {@link Log} reference.
+     * @return initialized optionsBuilder.
+     */
+    OptionsBuilder create(AsciidoctorMojo configuration, MavenProject mavenProject, Log log) {
+
+        final OptionsBuilder optionsBuilder = Options.builder()
+            .backend(configuration.getBackend())
+            .safe(SafeMode.UNSAFE)
+            .standalone(configuration.standalone)
+            .mkDirs(true);
+
+        if (!isBlank(configuration.getEruby()))
+            optionsBuilder.eruby(configuration.getEruby());
+
+        if (configuration.isSourcemap())
+            optionsBuilder.option(Options.SOURCEMAP, true);
+
+        if (configuration.isCatalogAssets())
+            optionsBuilder.option(Options.CATALOG_ASSETS, true);
+
+        if (!configuration.isTemplateCache())
+            optionsBuilder.option(Options.TEMPLATE_CACHE, false);
+
+        if (configuration.getDoctype() != null)
+            optionsBuilder.docType(configuration.getDoctype());
+
+        if (configuration.getTemplateEngine() != null)
+            optionsBuilder.templateEngine(configuration.getTemplateEngine());
+
+        if (!configuration.getTemplateDirs().isEmpty())
+            optionsBuilder.templateDirs(configuration.getTemplateDirs().toArray(new File[]{}));
+
+        final Attributes attributes = createAttributes(configuration, mavenProject, log);
+        return optionsBuilder.attributes(attributes);
+    }
+
+}
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorRefreshMojo.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorRefreshMojo.java
index da4c385..7f5cae5 100644
--- a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorRefreshMojo.java
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorRefreshMojo.java
@@ -1,5 +1,6 @@
 package org.asciidoctor.maven;
 
+import javax.inject.Inject;
 import java.io.File;
 import java.io.FileFilter;
 import java.util.Collection;
@@ -19,6 +20,8 @@ import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
+import org.asciidoctor.maven.process.ResourcesProcessor;
+import org.asciidoctor.maven.process.SourceDocumentFinder;
 import org.asciidoctor.maven.refresh.AdditionalSourceFileAlterationListenerAdaptor;
 import org.asciidoctor.maven.refresh.AsciidoctorConverterFileAlterationListenerAdaptor;
 import org.asciidoctor.maven.refresh.ResourceCopyFileAlterationListenerAdaptor;
@@ -26,9 +29,7 @@ import org.asciidoctor.maven.refresh.ResourcesPatternBuilder;
 import org.asciidoctor.maven.refresh.TimeCounter;
 
 import static org.asciidoctor.maven.commons.StringUtils.isNotBlank;
-import static org.asciidoctor.maven.process.SourceDocumentFinder.CUSTOM_FILE_EXTENSIONS_PATTERN_PREFIX;
-import static org.asciidoctor.maven.process.SourceDocumentFinder.CUSTOM_FILE_EXTENSIONS_PATTERN_SUFFIX;
-import static org.asciidoctor.maven.process.SourceDocumentFinder.STANDARD_FILE_EXTENSIONS_PATTERN;
+import static org.asciidoctor.maven.process.SourceDocumentFinder.*;
 
 @Mojo(name = "auto-refresh")
 public class AsciidoctorRefreshMojo extends AsciidoctorMojo {
@@ -43,6 +44,10 @@ public class AsciidoctorRefreshMojo extends AsciidoctorMojo {
 
     private Collection<FileAlterationMonitor> monitors = null;
 
+    @Inject
+    public AsciidoctorRefreshMojo(AsciidoctorJFactory asciidoctorJFactory, AsciidoctorOptionsFactory asciidoctorOptionsFactory, SourceDocumentFinder finder, ResourcesProcessor defaultResourcesProcessor) {
+        super(asciidoctorJFactory, asciidoctorOptionsFactory, finder, defaultResourcesProcessor);
+    }
 
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
@@ -55,7 +60,7 @@ public class AsciidoctorRefreshMojo extends AsciidoctorMojo {
     protected void doWork() {
         long timeInMillis = TimeCounter.timed(() -> {
             try {
-                processAllSources(defaultResourcesProcessor);
+                processAllSources(resourcesProcessor);
             } catch (MojoExecutionException e) {
                 getLog().error(e);
             }
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorZipMojo.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorZipMojo.java
index 0557fec..87f9443 100644
--- a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorZipMojo.java
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorZipMojo.java
@@ -10,10 +10,15 @@ import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProjectHelper;
 import org.asciidoctor.maven.io.Zips;
+import org.asciidoctor.maven.process.ResourcesProcessor;
+import org.asciidoctor.maven.process.SourceDocumentFinder;
+
+import javax.inject.Inject;
 
 @Deprecated(since = "3.0.0", forRemoval = true)
 @Mojo(name = "zip")
 public class AsciidoctorZipMojo extends AsciidoctorMojo {
+
     public static final String PREFIX = AsciidoctorMaven.PREFIX + "zip.";
 
     @Component
@@ -32,6 +37,11 @@ public class AsciidoctorZipMojo extends AsciidoctorMojo {
     @Parameter(property = PREFIX + "zipClassifier", defaultValue = "asciidoctor")
     protected String zipClassifier;
 
+    @Inject
+    public AsciidoctorZipMojo(AsciidoctorJFactory asciidoctorJFactory, AsciidoctorOptionsFactory asciidoctorOptionsFactory, SourceDocumentFinder finder, ResourcesProcessor defaultResourcesProcessor) {
+        super(asciidoctorJFactory, asciidoctorOptionsFactory, finder, defaultResourcesProcessor);
+    }
+
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
         super.execute();
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/http/AsciidoctorHandler.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/http/AsciidoctorHandler.java
index 8744b35..d1827e2 100644
--- a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/http/AsciidoctorHandler.java
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/http/AsciidoctorHandler.java
@@ -21,7 +21,7 @@ import io.netty.util.CharsetUtil;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 
-public class AsciidoctorHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
+class AsciidoctorHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
 
     private static final String HTML_MEDIA_TYPE = "text/html";
     public static final String HTML_EXTENSION = ".html";
@@ -29,7 +29,7 @@ public class AsciidoctorHandler extends SimpleChannelInboundHandler<FullHttpRequ
     private final File directory;
     private final String defaultPage;
 
-    public AsciidoctorHandler(final File workDir, final String defaultPage) {
+    AsciidoctorHandler(final File workDir, final String defaultPage) {
         this.directory = workDir;
 
         if (defaultPage.contains(".")) {
@@ -40,7 +40,7 @@ public class AsciidoctorHandler extends SimpleChannelInboundHandler<FullHttpRequ
     }
 
     @Override
-    public void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest msg) throws Exception {
+    protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest msg) throws Exception {
 
         if (msg.method() != HttpMethod.GET && msg.method() != HttpMethod.HEAD) {
             send(ctx, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.METHOD_NOT_ALLOWED));
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/CopyResourcesProcessor.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/CopyResourcesProcessor.java
index 9e5126a..58329a1 100644
--- a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/CopyResourcesProcessor.java
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/CopyResourcesProcessor.java
@@ -1,5 +1,6 @@
 package org.asciidoctor.maven.process;
 
+import javax.inject.Named;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -22,7 +23,10 @@ import static org.asciidoctor.maven.commons.StringUtils.isNotBlank;
  * - AsciiDoc documents: based on file extension.
  * - Asciidoctor Docinfo files.
  * - Internal files and folders: those not starting with underscore '_'.
+ *
+ * @since 3.0.0
  */
+@Named
 public class CopyResourcesProcessor implements ResourcesProcessor {
 
     // docinfo snippets should not be copied
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/ResourcesProcessor.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/ResourcesProcessor.java
index a542461..97e9421 100644
--- a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/ResourcesProcessor.java
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/ResourcesProcessor.java
@@ -4,6 +4,9 @@ import java.io.File;
 
 import org.asciidoctor.maven.AsciidoctorMojo;
 
+/**
+ * @since 2.1.0
+ */
 public interface ResourcesProcessor {
 
     /**
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/SourceDirectoryFinder.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/SourceDirectoryFinder.java
index 248f366..32b1e4a 100644
--- a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/SourceDirectoryFinder.java
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/SourceDirectoryFinder.java
@@ -10,6 +10,7 @@ import java.util.function.Consumer;
  * In that case, it only checks availability for the value provided as initial.
  *
  * @author abelsromero
+ * @since 2.0.0
  */
 public class SourceDirectoryFinder {
 
diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/SourceDocumentFinder.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/SourceDocumentFinder.java
index 25b42f9..62bc071 100644
--- a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/SourceDocumentFinder.java
+++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/process/SourceDocumentFinder.java
@@ -1,5 +1,6 @@
 package org.asciidoctor.maven.process;
 
+import javax.inject.Singleton;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -18,6 +19,7 @@ import java.util.stream.Stream;
  *
  * @author stdll
  */
+@Singleton
 public class SourceDocumentFinder {
 
     // copied from org.asciidoctor.AsciiDocDirectoryWalker.ASCIIDOC_REG_EXP_EXTENSION
diff --git a/asciidoctor-parser-doxia-module/src/main/java/org/asciidoctor/maven/site/parser/AsciidoctorAstDoxiaParser.java b/asciidoctor-parser-doxia-module/src/main/java/org/asciidoctor/maven/site/parser/AsciidoctorAstDoxiaParser.java
index bc0602c..f1c035b 100644
--- a/asciidoctor-parser-doxia-module/src/main/java/org/asciidoctor/maven/site/parser/AsciidoctorAstDoxiaParser.java
+++ b/asciidoctor-parser-doxia-module/src/main/java/org/asciidoctor/maven/site/parser/AsciidoctorAstDoxiaParser.java
@@ -1,11 +1,9 @@
 package org.asciidoctor.maven.site.parser;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 import java.io.File;
 import java.io.IOException;
 import java.io.Reader;
-import java.util.logging.Logger;
 
 import org.apache.maven.doxia.parser.AbstractTextParser;
 import org.apache.maven.doxia.parser.ParseException;
@@ -13,29 +11,24 @@ import org.apache.maven.doxia.parser.Parser;
 import org.apache.maven.doxia.sink.Sink;
 import org.apache.maven.project.MavenProject;
 import org.asciidoctor.Asciidoctor;
-import org.asciidoctor.Attributes;
-import org.asciidoctor.AttributesBuilder;
-import org.asciidoctor.Options;
-import org.asciidoctor.OptionsBuilder;
-import org.asciidoctor.SafeMode;
 import org.asciidoctor.ast.Document;
 import org.asciidoctor.maven.commons.StringUtils;
 import org.asciidoctor.maven.log.LogHandler;
-import org.asciidoctor.maven.log.LogRecordFormatter;
 import org.asciidoctor.maven.log.LogRecordsProcessors;
 import org.asciidoctor.maven.log.MemoryLogHandler;
 import org.asciidoctor.maven.site.HeadParser;
 import org.asciidoctor.maven.site.HeaderMetadata;
+import org.asciidoctor.maven.site.LogHandlerFactory;
 import org.asciidoctor.maven.site.SiteConversionConfiguration;
 import org.asciidoctor.maven.site.SiteConversionConfigurationParser;
-import org.asciidoctor.maven.site.SiteLogHandlerDeserializer;
 import org.codehaus.plexus.component.annotations.Component;
 import org.codehaus.plexus.util.IOUtil;
 import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static org.asciidoctor.maven.commons.StringUtils.isNotBlank;
-import static org.asciidoctor.maven.site.SiteBaseDirResolver.resolveBaseDir;
+
 
 /**
  * This class is used by <a href="https://maven.apache.org/doxia/overview.html">the Doxia framework</a>
@@ -48,16 +41,26 @@ import static org.asciidoctor.maven.site.SiteBaseDirResolver.resolveBaseDir;
 @Component(role = Parser.class, hint = AsciidoctorAstDoxiaParser.ROLE_HINT)
 public class AsciidoctorAstDoxiaParser extends AbstractTextParser {
 
-    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(AsciidoctorAstDoxiaParser.class);
-
-    @Inject
-    protected Provider<MavenProject> mavenProjectProvider;
-
     /**
      * The role hint for the {@link AsciidoctorAstDoxiaParser} Plexus component.
      */
     public static final String ROLE_HINT = "asciidoc";
 
+    private static final Logger logger = LoggerFactory.getLogger(AsciidoctorAstDoxiaParser.class);
+
+    private final MavenProject mavenProject;
+    private final SiteConversionConfigurationParser siteConfigParser;
+    private final LogHandlerFactory logHandlerFactory;
+
+    @Inject
+    public AsciidoctorAstDoxiaParser(MavenProject mavenProject,
+                                     SiteConversionConfigurationParser siteConfigParser,
+                                     LogHandlerFactory logHandlerFactory) {
+        this.mavenProject = mavenProject;
+        this.siteConfigParser = siteConfigParser;
+        this.logHandlerFactory = logHandlerFactory;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -73,9 +76,9 @@ public class AsciidoctorAstDoxiaParser extends AbstractTextParser {
             return;
         }
 
-        final MavenProject project = mavenProjectProvider.get();
-        final Xpp3Dom siteConfig = getSiteConfig(project);
-        final File siteDirectory = resolveBaseDir(project.getBasedir(), siteConfig);
+        final SiteConversionConfiguration conversionConfig = siteConfigParser.processAsciiDocConfig(mavenProject, ROLE_HINT);
+        final Xpp3Dom siteConfig = conversionConfig.getAsciidocConfig();
+        final File siteDirectory = conversionConfig.getSiteBaseDir();
 
         // Doxia handles a single instance of this class and invokes it multiple times.
         // We need to ensure certain elements are initialized only once to avoid errors.
@@ -83,19 +86,17 @@ public class AsciidoctorAstDoxiaParser extends AbstractTextParser {
         // And overriding init and other methods form parent classes does not work.
         final Asciidoctor asciidoctor = Asciidoctor.Factory.create();
 
-        SiteConversionConfiguration conversionConfig = new SiteConversionConfigurationParser(project)
-            .processAsciiDocConfig(siteConfig, defaultOptions(siteDirectory), defaultAttributes());
         for (String require : conversionConfig.getRequires()) {
             requireLibrary(asciidoctor, require);
         }
 
-        final LogHandler logHandler = getLogHandlerConfig(siteConfig);
-        final MemoryLogHandler memoryLogHandler = asciidoctorLoggingSetup(asciidoctor, siteDirectory);
-
         if (isNotBlank(reference))
             logger.debug("Document loaded: {}", reference);
 
-        Document document = asciidoctor.load(source, conversionConfig.getOptions());
+        final LogHandler logHandler = logHandlerFactory.getConfiguration(siteConfig);
+        final MemoryLogHandler memoryLogHandler = logHandlerFactory.create(asciidoctor, siteDirectory, logger);
+
+        final Document document = asciidoctor.load(source, conversionConfig.getOptions());
 
         try {
             // process log messages according to mojo configuration
@@ -104,54 +105,20 @@ public class AsciidoctorAstDoxiaParser extends AbstractTextParser {
                 if (logHandler.getOutputToConsole() && StringUtils.isNotBlank(reference)) {
                     memoryLogHandler.processAll();
                 }
-                new LogRecordsProcessors(logHandler, siteDirectory, errorMessage -> logger.error(errorMessage))
+                new LogRecordsProcessors(logHandler, siteDirectory, logger::error)
                     .processLogRecords(memoryLogHandler);
             }
         } catch (Exception exception) {
             throw new ParseException(exception.getMessage(), exception);
         }
 
-        HeaderMetadata headerMetadata = HeaderMetadata.from(document);
-
         new HeadParser(sink)
-            .parse(headerMetadata);
+            .parse(HeaderMetadata.from(document));
 
         new NodeSinker(sink)
             .sink(document);
     }
 
-    private MemoryLogHandler asciidoctorLoggingSetup(Asciidoctor asciidoctor, File siteDirectory) {
-
-        final MemoryLogHandler memoryLogHandler = new MemoryLogHandler(false,
-            logRecord -> logger.info(LogRecordFormatter.format(logRecord, siteDirectory)));
-        asciidoctor.registerLogHandler(memoryLogHandler);
-        // disable default console output of AsciidoctorJ
-        Logger.getLogger("asciidoctor").setUseParentHandlers(false);
-        return memoryLogHandler;
-    }
-
-    private LogHandler getLogHandlerConfig(Xpp3Dom siteConfig) {
-        Xpp3Dom asciidoc = siteConfig == null ? null : siteConfig.getChild("asciidoc");
-        return new SiteLogHandlerDeserializer().deserialize(asciidoc);
-    }
-
-    protected Xpp3Dom getSiteConfig(MavenProject project) {
-        return project.getGoalConfiguration("org.apache.maven.plugins", "maven-site-plugin", "site", "site");
-    }
-
-    protected OptionsBuilder defaultOptions(File siteDirectory) {
-        return Options.builder()
-            .backend("xhtml")
-            .safe(SafeMode.UNSAFE)
-            .baseDir(new File(siteDirectory, ROLE_HINT));
-    }
-
-    protected AttributesBuilder defaultAttributes() {
-        return Attributes.builder()
-            .attribute("idprefix", "@")
-            .attribute("showtitle", "@");
-    }
-
     private void requireLibrary(Asciidoctor asciidoctor, String require) {
         if (!(require = require.trim()).isEmpty()) {
             try {
diff --git a/pom.xml b/pom.xml
index 6c1c75e..48d8315 100644
--- a/pom.xml
+++ b/pom.xml
@@ -74,6 +74,7 @@
         <doxia.version>2.0.0</doxia.version>
         <doxia.sitetools.version>2.0.0</doxia.sitetools.version>
         <plexus-component-metadata.version>2.2.0</plexus-component-metadata.version>
+        <plexus-utils.version>3.5.1</plexus-utils.version>
         <asciidoctorj.version>3.0.0</asciidoctorj.version>
         <jruby.version>9.4.8.0</jruby.version>
     </properties>
@@ -146,6 +147,20 @@
                     <release>${project.java.version}</release>
                 </configuration>
             </plugin>
+            <!-- Required to use @Named for DI -->
+            <plugin>
+                <groupId>org.eclipse.sisu</groupId>
+                <artifactId>sisu-maven-plugin</artifactId>
+                <version>0.9.0.M3</version>
+                <executions>
+                    <execution>
+                        <id>generate-index</id>
+                        <goals>
+                            <goal>main-index</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
 
         <pluginManagement>
@@ -234,6 +249,32 @@
     </build>
 
     <profiles>
+        <profile>
+            <id>maven4</id>
+            <properties>
+                <maven.version>4.0.0-beta-5</maven.version>
+                <plexus-utils.version>4.0.2</plexus-utils.version>
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>org.codehaus.plexus</groupId>
+                    <artifactId>plexus-xml</artifactId>
+                    <version>4.0.4</version>
+                </dependency>
+            </dependencies>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <version>3.13.0</version>
+                        <configuration>
+                            <release>17</release>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
         <profile>
             <!--
               To release, define environment variables:


Test patch:
diff --git a/asciidoctor-converter-doxia-module/src/test/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParserTest.java b/asciidoctor-converter-doxia-module/src/test/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParserTest.java
index 1c485b9..3ce2c77 100644
--- a/asciidoctor-converter-doxia-module/src/test/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParserTest.java
+++ b/asciidoctor-converter-doxia-module/src/test/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParserTest.java
@@ -19,7 +19,6 @@ import org.mockito.Mockito;
 import static org.asciidoctor.maven.site.AsciidoctorConverterDoxiaParser.ROLE_HINT;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.catchThrowable;
-import static org.codehaus.plexus.util.ReflectionUtils.setVariableValueInObject;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
@@ -247,14 +246,14 @@ class AsciidoctorConverterDoxiaParserTest {
     }
 
     @SneakyThrows
-    private javax.inject.Provider<MavenProject> createMavenProjectMock(String configuration) {
+    private MavenProject createMockMavenProject(String configuration) {
         MavenProject mockProject = Mockito.mock(MavenProject.class);
         when(mockProject.getBasedir())
             .thenReturn(new File("."));
         when(mockProject.getGoalConfiguration(anyString(), anyString(), anyString(), anyString()))
             .thenReturn(configuration != null ? Xpp3DomBuilder.build(new StringReader(configuration)) : null);
 
-        return () -> mockProject;
+        return mockProject;
     }
 
     @SneakyThrows
@@ -264,16 +263,19 @@ class AsciidoctorConverterDoxiaParserTest {
 
     @SneakyThrows
     private AsciidoctorConverterDoxiaParser mockAsciidoctorDoxiaParser(String configuration) {
-        AsciidoctorConverterDoxiaParser parser = new AsciidoctorConverterDoxiaParser();
-        setVariableValueInObject(parser, "mavenProjectProvider", createMavenProjectMock(configuration));
-        return parser;
+        return new AsciidoctorConverterDoxiaParser(
+            createMockMavenProject(configuration),
+            new SiteConversionConfigurationParser(new SiteBaseDirResolver()),
+            new LogHandlerFactory(),
+            new SiteConverterDecorator()
+        );
     }
 
     private Sink createSinkMock() {
         return new TextProviderSink();
     }
 
-    class TextProviderSink extends AbstractTextSink {
+    static class TextProviderSink extends AbstractTextSink {
         String text;
 
         @Override
diff --git a/asciidoctor-converter-doxia-module/src/test/java/org/asciidoctor/maven/site/SiteConverterDecoratorTest.java b/asciidoctor-converter-doxia-module/src/test/java/org/asciidoctor/maven/site/SiteConverterDecoratorTest.java
index db098b8..0e43275 100644
--- a/asciidoctor-converter-doxia-module/src/test/java/org/asciidoctor/maven/site/SiteConverterDecoratorTest.java
+++ b/asciidoctor-converter-doxia-module/src/test/java/org/asciidoctor/maven/site/SiteConverterDecoratorTest.java
@@ -28,10 +28,10 @@ class SiteConverterDecoratorTest {
         "Hello, AsciiDoc!\n================"
     })
     void should_extract_title_from_header(String title) {
-        SiteConverterDecorator siteConverter = new SiteConverterDecorator(asciidoctor);
+        SiteConverterDecorator siteConverter = new SiteConverterDecorator();
 
         Options options = defaultOptions();
-        Result result = siteConverter.process(title + "\n", options);
+        Result result = siteConverter.process(asciidoctor, title + "\n", options);
 
         HeaderMetadata headerMetadata = result.getHeaderMetadata();
         assertThat(headerMetadata.getTitle()).isEqualTo("Hello, AsciiDoc!");
@@ -40,7 +40,7 @@ class SiteConverterDecoratorTest {
 
     @Test
     void should_extract_title_from_attribute() {
-        SiteConverterDecorator siteConverter = new SiteConverterDecorator(asciidoctor);
+        SiteConverterDecorator siteConverter = new SiteConverterDecorator();
 
         Options options = Options.builder()
             .safe(SafeMode.UNSAFE)
@@ -51,7 +51,7 @@ class SiteConverterDecoratorTest {
                 .attribute("who", "me")
                 .build())
             .build();
-        Result result = siteConverter.process("= Hello, {who}!\n", options);
+        Result result = siteConverter.process(asciidoctor, "= Hello, {who}!\n", options);
 
         HeaderMetadata headerMetadata = result.getHeaderMetadata();
         assertThat(headerMetadata.getTitle()).isEqualTo("Hello, me!");
@@ -62,10 +62,10 @@ class SiteConverterDecoratorTest {
     @ParameterizedTest
     @MethodSource("authorsProvider")
     void should_extract_author(String content, String expected) {
-        SiteConverterDecorator siteConverter = new SiteConverterDecorator(asciidoctor);
+        SiteConverterDecorator siteConverter = new SiteConverterDecorator();
 
         Options options = defaultOptions();
-        Result result = siteConverter.process(content + "\n", options);
+        Result result = siteConverter.process(asciidoctor, content + "\n", options);
 
         HeaderMetadata headerMetadata = result.getHeaderMetadata();
         assertThat(headerMetadata.getAuthors())
@@ -84,11 +84,11 @@ class SiteConverterDecoratorTest {
 
     @Test
     void should_extract_author_from_attribute() {
-        SiteConverterDecorator siteConverter = new SiteConverterDecorator(asciidoctor);
+        SiteConverterDecorator siteConverter = new SiteConverterDecorator();
 
         String content = "= Hello, AsciiDoc!";
         Options options = optionsWithAttributes(Collections.singletonMap("author", "From Attr"));
-        Result result = siteConverter.process(content + "\n", options);
+        Result result = siteConverter.process(asciidoctor, content + "\n", options);
 
         HeaderMetadata headerMetadata = result.getHeaderMetadata();
         assertThat(headerMetadata.getAuthors())
@@ -98,10 +98,10 @@ class SiteConverterDecoratorTest {
 
     @Test
     void should_extract_multiple_authors() {
-        SiteConverterDecorator siteConverter = new SiteConverterDecorator(asciidoctor);
+        SiteConverterDecorator siteConverter = new SiteConverterDecorator();
 
         String content = "= Hello, AsciiDoc!\nfirstname1 lastname2; firstname3 middlename4 lastname5";
-        Result result = siteConverter.process(content + "\n", defaultOptions());
+        Result result = siteConverter.process(asciidoctor, content + "\n", defaultOptions());
 
         HeaderMetadata headerMetadata = result.getHeaderMetadata();
         assertThat(headerMetadata.getAuthors())
@@ -111,10 +111,10 @@ class SiteConverterDecoratorTest {
 
     @Test
     void should_extract_datetime_generated() {
-        SiteConverterDecorator siteConverter = new SiteConverterDecorator(asciidoctor);
+        SiteConverterDecorator siteConverter = new SiteConverterDecorator();
 
         String content = "= Hello, AsciiDoc!";
-        Result result = siteConverter.process(content + "\n", defaultOptions());
+        Result result = siteConverter.process(asciidoctor, content + "\n", defaultOptions());
 
         HeaderMetadata headerMetadata = result.getHeaderMetadata();
         assertThat(headerMetadata.getDateTime()).matches("(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}) .*");
@@ -123,11 +123,11 @@ class SiteConverterDecoratorTest {
 
     @Test
     void should_extract_datetime_from_attribute() {
-        SiteConverterDecorator siteConverter = new SiteConverterDecorator(asciidoctor);
+        SiteConverterDecorator siteConverter = new SiteConverterDecorator();
 
         String content = "= Hello, AsciiDoc!";
         Options options = optionsWithAttributes(Collections.singletonMap("docdatetime", "2024-11-22"));
-        Result result = siteConverter.process(content + "\n", options);
+        Result result = siteConverter.process(asciidoctor, content + "\n", options);
 
         HeaderMetadata headerMetadata = result.getHeaderMetadata();
         assertThat(headerMetadata.getDateTime()).isEqualTo("2024-11-22");
diff --git a/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/site/SiteConversionConfigurationParserTest.java b/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/site/SiteConversionConfigurationParserTest.java
index fd96e02..0d667f3 100644
--- a/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/site/SiteConversionConfigurationParserTest.java
+++ b/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/site/SiteConversionConfigurationParserTest.java
@@ -4,392 +4,411 @@ import java.io.File;
 import java.util.AbstractMap;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import org.apache.maven.model.Build;
 import org.apache.maven.model.Model;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.PluginExecution;
 import org.apache.maven.project.MavenProject;
-import org.asciidoctor.Attributes;
-import org.asciidoctor.AttributesBuilder;
-import org.asciidoctor.Options;
-import org.asciidoctor.OptionsBuilder;
+import org.asciidoctor.SafeMode;
+import org.assertj.core.data.MapEntry;
 import org.codehaus.plexus.util.xml.Xpp3Dom;
 import org.junit.jupiter.api.Test;
 
-import static org.asciidoctor.Options.ATTRIBUTES;
-import static org.asciidoctor.Options.BASEDIR;
-import static org.asciidoctor.Options.TEMPLATE_DIRS;
+import static org.asciidoctor.Options.*;
+import static org.asciidoctor.maven.site.SiteConversionConfigurationParserTest.FakeMavenProjectBuilder.fakeMavenProjectBuilder;
 import static org.assertj.core.api.Assertions.assertThat;
 
 class SiteConversionConfigurationParserTest {
 
+    private static final String ROLE_HINT = "asciidoc";
+
+    final SiteBaseDirResolver siteBaseDirResolver = new SiteBaseDirResolver();
+    final SiteConversionConfigurationParser configParser = new SiteConversionConfigurationParser(siteBaseDirResolver);
+
     @Test
     void should_return_default_configuration_when_site_xml_is_null() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
+        final MavenProject project = fakeMavenProjectBuilder().build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(null, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
-        final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES);
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNull();
+        assertContainsDefaultOptions(configuration);
+        assertContainsDefaultAttributes(configuration);
         assertThat(configuration.getRequires()).isEmpty();
     }
 
     @Test
     void should_return_default_configuration_when_asciidoc_xml_is_null() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.siteNode()
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.siteNode().build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
+
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
-        final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES);
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNull();
+        final Map<String, Object> optionsMap = assertContainsDefaultAttributes(configuration);
+        assertThat((String) optionsMap.get(BACKEND)).isEqualTo("xhtml");
+        assertThat((String) optionsMap.get(BASEDIR)).endsWith(defaultPluginSources());
+        assertThat((Integer) optionsMap.get(SAFE)).isEqualTo(SafeMode.UNSAFE.getLevel());
         assertThat(configuration.getRequires()).isEmpty();
     }
 
+    // TODO this should be the same?
+    private CharSequence defaultSiteSources() {
+        return String.join(File.separator, new String[]{"src", "site"});
+    }
+
+    private CharSequence defaultPluginSources() {
+        return String.join(File.separator, new String[]{"src", "site", "asciidoc"});
+    }
+
     @Test
     void should_return_simple_single_requires() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("requires")
-                .addChild("require", "gem")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("requires")
+            .addChild("require", "gem")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
-        final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES);
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultOptions(configuration);
+        assertContainsDefaultAttributes(configuration);
         assertThat(configuration.getRequires())
-                .containsExactly("gem");
+            .containsExactly("gem");
     }
 
     @Test
     void should_return_multiple_requires() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("requires")
-                .addChild("require", "gem_1", "gem_2", "gem_3")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("requires")
+            .addChild("require", "gem_1", "gem_2", "gem_3")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
-        final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES);
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultOptions(configuration);
+        assertContainsDefaultAttributes(configuration);
         assertThat(configuration.getRequires())
-                .containsExactlyInAnyOrder("gem_1", "gem_2", "gem_3");
+            .containsExactlyInAnyOrder("gem_1", "gem_2", "gem_3");
     }
 
     @Test
     void should_return_multiple_requires_when_defined_in_single_element() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("requires")
-                .addChild("require", "gem_1,gem_2, gem_3")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("requires")
+            .addChild("require", "gem_1,gem_2, gem_3")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
-        final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES);
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultOptions(configuration);
+        assertContainsDefaultAttributes(configuration);
         assertThat(configuration.getRequires())
-                .containsExactlyInAnyOrder("gem_1", "gem_2", "gem_3");
+            .containsExactlyInAnyOrder("gem_1", "gem_2", "gem_3");
     }
 
     @Test
     void should_remove_empty_and_blank_requires() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("requires")
-                .addChild("require", "gem_1,,gem_2", "", ",,", "gem_3")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("requires")
+            .addChild("require", "gem_1,,gem_2", "", ",,", "gem_3")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
-        final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES);
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultOptions(configuration);
+        assertContainsDefaultAttributes(configuration);
         assertThat(configuration.getRequires())
-                .containsExactlyInAnyOrder("gem_1", "gem_2", "gem_3");
+            .containsExactlyInAnyOrder("gem_1", "gem_2", "gem_3");
     }
 
     @Test
     void should_return_attributes() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("attributes")
-                .addChild("imagesdir", "./images")
-                .parent().addChild("source-highlighter", "coderay")
-                .parent().addChild("sectnums")
-                .parent().addChild("toc")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("attributes")
+            .addChild("imagesdir", "./images")
+            .parent().addChild("source-highlighter", "coderay")
+            .parent().addChild("sectnums")
+            .parent().addChild("toc")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultOptions(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
         Map attributes = (Map) configuration.getOptions()
-                .map()
-                .get(ATTRIBUTES);
-        assertThat(attributes).
-                containsExactlyInAnyOrderEntriesOf(map(
-                        entry("imagesdir", "./images"),
-                        entry("source-highlighter", "coderay"),
-                        entry("sectnums", ""),
-                        entry("toc", "")
-                ));
+            .map()
+            .get(ATTRIBUTES);
+        assertThat(attributes).containsExactlyInAnyOrderEntriesOf(map(
+            entry("idprefix", "@"),
+            entry("imagesdir", "./images"),
+            entry("sectnums", ""),
+            entry("showtitle", "@"),
+            entry("source-highlighter", "coderay"),
+            entry("toc", "")
+        ));
     }
 
     @Test
     void should_map_null_attributes_as_empty_string() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("attributes")
-                .addChild("toc")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("attributes")
+            .addChild("toc")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
-        Map attributes = (Map) configuration
-                .getOptions()
-                .map()
-                .get(ATTRIBUTES);
-        assertThat(attributes).
-                containsExactlyInAnyOrderEntriesOf(map(
-                        entry("toc", "")
-                ));
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultOptions(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
+        Map attributes = (Map) configuration.getOptions()
+            .map()
+            .get(ATTRIBUTES);
+        assertThat(attributes).containsExactlyInAnyOrderEntriesOf(map(
+            entry("idprefix", "@"),
+            entry("showtitle", "@"),
+            entry("toc", "")
+        ));
+
     }
 
     @Test
     void should_map_true_boolean_attribute_as_empty_string_value() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("attributes")
-                .addChild("toc", "true")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("attributes")
+            .addChild("toc", "true")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultOptions(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
         Map attributes = (Map) configuration
-                .getOptions()
-                .map()
-                .get(ATTRIBUTES);
-        assertThat(attributes).
-                containsExactlyInAnyOrderEntriesOf(map(
-                        entry("toc", "")
-                ));
+            .getOptions()
+            .map()
+            .get(ATTRIBUTES);
+        assertThat(attributes).containsExactlyInAnyOrderEntriesOf(map(
+            entry("idprefix", "@"),
+            entry("showtitle", "@"),
+            entry("toc", "")
+        ));
     }
 
     @Test
     void should_map_false_boolean_attribute_as_null_value() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("attributes")
-                .addChild("toc", "false")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("attributes")
+            .addChild("toc", "false")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
-        final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap)
-                .hasSize(1);
-        Map attributes = (Map) optionsMap.get(ATTRIBUTES);
-        assertThat(attributes).
-                containsExactlyInAnyOrderEntriesOf(map(
-                        entry("toc", null)
-                ));
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultOptions(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
+        Map attributes = (Map) configuration
+            .getOptions()
+            .map()
+            .get(ATTRIBUTES);
+        assertThat(attributes).containsExactlyInAnyOrderEntriesOf(map(
+            entry("idprefix", "@"),
+            entry("showtitle", "@"),
+            entry("toc", null)
+        ));
     }
 
     @Test
     void should_return_template_dirs_when_defined_as_templateDirs_dir() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("templateDirs")
-                .addChild("dir", "path")
-                .parent()
-                .addChild("dir", "path2")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("templateDirs")
+            .addChild("dir", "path")
+            .parent()
+            .addChild("dir", "path2")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultAttributes(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
+
         final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES, TEMPLATE_DIRS);
+        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES, BACKEND, BASEDIR, SAFE, TEMPLATE_DIRS);
         assertThat(optionsMap.get(TEMPLATE_DIRS))
-                .isEqualTo(Arrays.asList(
-                        new File("path").getAbsolutePath(),
-                        new File("path2").getAbsolutePath()
-                ));
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
+            .isEqualTo(Arrays.asList(
+                new File("path").getAbsolutePath(),
+                new File("path2").getAbsolutePath()
+            ));
     }
 
     @Test
     void should_return_template_dirs_when_defined_as_template_dirs_dir() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("template_dirs")
-                .addChild("dir", "path")
-                .parent()
-                .addChild("dir", "path2")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("template_dirs")
+            .addChild("dir", "path")
+            .parent()
+            .addChild("dir", "path2")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultAttributes(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
+
         final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES, TEMPLATE_DIRS);
+        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES, BACKEND, BASEDIR, SAFE, TEMPLATE_DIRS);
         assertThat(optionsMap.get(TEMPLATE_DIRS))
-                .isEqualTo(Arrays.asList(
-                        new File("path").getAbsolutePath(),
-                        new File("path2").getAbsolutePath()
-                ));
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
+            .isEqualTo(Arrays.asList(
+                new File("path").getAbsolutePath(),
+                new File("path2").getAbsolutePath()
+            ));
     }
 
     @Test
     void should_not_return_empty_template_dirs() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("template_dirs")
-                .addChild("dir", "")
-                .parent()
-                .addChild("dir")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("template_dirs")
+            .addChild("dir", "")
+            .parent()
+            .addChild("dir")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
-        // then
-        final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES);
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
+        // then: BASE_DIR option is not even added
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultOptions(configuration);
+        assertContainsDefaultAttributes(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
     }
 
     @Test
     void should_return_baseDir_dirs_when_defined_as_template_dirs_dir() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("baseDir", "path")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("baseDir", "path")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
-        final Map<String, Object> optionsMap = configuration.getOptions().map();
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultAttributes(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
 
-        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES, BASEDIR);
+        final Map<String, Object> optionsMap = configuration.getOptions().map();
+        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES, BACKEND, BASEDIR, SAFE);
+        assertThat((String) optionsMap.get(BACKEND)).isEqualTo("xhtml");
+        assertThat((Integer) optionsMap.get(SAFE)).isEqualTo(SafeMode.UNSAFE.getLevel());
         assertThat(optionsMap.get(BASEDIR))
-                .isEqualTo(new File("path").getAbsolutePath());
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
+            .isEqualTo(new File("path").getAbsolutePath());
     }
 
     @Test
     void should_return_any_configuration_inside_asciidoc_node_as_option() {
         // given
-        final MavenProject project = fakeProject();
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
-                .addChild("option-1", "value-1")
-                .parent().addChild("option_2", "value-2")
-                .parent().addChild("_option-3", "value-3")
-                .parent().addChild("option-4_", "value-4")
-                .parent().addChild("option.5", "value-5")
-                .build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode()
+            .addChild("option-1", "value-1")
+            .parent().addChild("option_2", "value-2")
+            .parent().addChild("_option-3", "value-3")
+            .parent().addChild("option-4_", "value-4")
+            .parent().addChild("option.5", "value-5")
+            .build();
+        final MavenProject project = fakeMavenProjectBuilder().siteConfig(siteConfig).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultAttributes(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
+
         final Map<String, Object> optionsMap = configuration.getOptions().map();
         assertThat(optionsMap)
-                .containsOnlyKeys(ATTRIBUTES, "option-1", "option_2", "_option-3", "option-4_", "option.5");
+            .containsOnlyKeys(
+                ATTRIBUTES, BACKEND, BASEDIR, SAFE,
+                "option-1", "option_2", "_option-3", "option-4_", "option.5");
         assertThat(optionsMap.get("option-1")).isEqualTo("value-1");
         assertThat(optionsMap.get("option_2")).isEqualTo("value-2");
         assertThat(optionsMap.get("_option-3")).isEqualTo("value-3");
         assertThat(optionsMap.get("option-4_")).isEqualTo("value-4");
         assertThat(optionsMap.get("option.5")).isEqualTo("value-5");
-        assertThat((Map) optionsMap.get(ATTRIBUTES)).isEmpty();
     }
 
     @Test
@@ -398,22 +417,24 @@ class SiteConversionConfigurationParserTest {
         final Map<String, String> projectProperties = new HashMap<>();
         projectProperties.put("mvn.property-test1", "value-1");
         projectProperties.put("mvn-property.test2", "value_2");
-        final MavenProject project = fakeProject(projectProperties);
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
+        final MavenProject project = fakeMavenProjectBuilder().properties(projectProperties).build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(null, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNull();
+        assertContainsDefaultOptions(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
+
         final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap)
-                .containsOnlyKeys(ATTRIBUTES);
         Map attributes = (Map) optionsMap.get(ATTRIBUTES);
         assertThat(attributes).containsExactlyInAnyOrderEntriesOf(map(
-                entry("mvn-property-test1", "value-1"),
-                entry("mvn-property-test2", "value_2")
+            entry("mvn-property-test1", "value-1"),
+            entry("mvn-property-test2", "value_2"),
+            entry("idprefix", "@"),
+            entry("showtitle", "@")
         ));
     }
 
@@ -423,40 +444,123 @@ class SiteConversionConfigurationParserTest {
         final Map<String, String> projectProperties = new HashMap<>();
         projectProperties.put("mvn.property-test1", "value-1");
         projectProperties.put("mvn-property.test2", "value_2");
-        final MavenProject project = fakeProject(projectProperties);
-        OptionsBuilder emptyOptions = Options.builder();
-        AttributesBuilder emptyAttributes = Attributes.builder();
-        Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode().build();
+        final Xpp3Dom siteConfig = Xpp3DoomBuilder.asciidocNode().build();
+        final MavenProject project = fakeMavenProjectBuilder()
+            .properties(projectProperties)
+            .siteConfig(siteConfig)
+            .build();
 
         // when
-        SiteConversionConfiguration configuration = new SiteConversionConfigurationParser(project)
-                .processAsciiDocConfig(siteConfig, emptyOptions, emptyAttributes);
+        SiteConversionConfiguration configuration = configParser.processAsciiDocConfig(project, ROLE_HINT);
 
         // then
+        assertThat(configuration.getSiteBaseDir().getPath()).endsWith(defaultSiteSources());
+        assertThat(configuration.getAsciidocConfig()).isNotNull();
+        assertContainsDefaultOptions(configuration);
+        assertThat(configuration.getRequires()).isEmpty();
+
         final Map<String, Object> optionsMap = configuration.getOptions().map();
-        assertThat(optionsMap)
-                .containsOnlyKeys(ATTRIBUTES);
         Map attributes = (Map) optionsMap.get(ATTRIBUTES);
         assertThat(attributes).containsExactlyInAnyOrderEntriesOf(map(
-                entry("mvn-property-test1", "value-1"),
-                entry("mvn-property-test2", "value_2")
+            entry("mvn-property-test1", "value-1"),
+            entry("mvn-property-test2", "value_2"),
+            entry("idprefix", "@"),
+            entry("showtitle", "@")
         ));
     }
 
+    private void assertContainsDefaultOptions(SiteConversionConfiguration configuration) {
+        final Map<String, Object> optionsMap = configuration.getOptions().map();
+        assertThat(optionsMap).containsOnlyKeys(ATTRIBUTES, BACKEND, BASEDIR, SAFE);
+        assertThat((String) optionsMap.get(BACKEND)).isEqualTo("xhtml");
+        assertThat((String) optionsMap.get(BASEDIR)).endsWith(defaultPluginSources());
+        assertThat((Integer) optionsMap.get(SAFE)).isEqualTo(SafeMode.UNSAFE.getLevel());
+    }
+
+    private static Map<String, Object> assertContainsDefaultAttributes(SiteConversionConfiguration configuration) {
+        final Map<String, Object> optionsMap = configuration.getOptions().map();
+        assertThat((Map) optionsMap.get(ATTRIBUTES)).containsExactly(
+            MapEntry.entry("idprefix", "@"),
+            MapEntry.entry("showtitle", "@")
+        );
+        return optionsMap;
+    }
+
+    static class FakeMavenProjectBuilder {
+        Xpp3Dom siteConfig = null;
+        Map<String, String> properties = null;
+
+        static FakeMavenProjectBuilder fakeMavenProjectBuilder() {
+            return new FakeMavenProjectBuilder();
+        }
+
+        FakeMavenProjectBuilder siteConfig(Xpp3Dom siteConfig) {
+            this.siteConfig = siteConfig;
+            return this;
+        }
+
+        FakeMavenProjectBuilder properties(Map<String, String> properties) {
+            this.properties = properties;
+            return this;
+        }
+
+        MavenProject build() {
+            final Model model = new Model();
+
+            if (properties != null) {
+                model.getProperties().putAll(properties);
+            }
+
+            final MavenProject project = new MavenProject(model);
+            project.setFile(new File(".").getAbsoluteFile());
+
+            final Build build = new Build();
+            if (siteConfig != null) {
+                final Plugin sitePlugin = new Plugin();
+                sitePlugin.setGroupId("org.apache.maven.plugins");
+                sitePlugin.setArtifactId("maven-site-plugin");
+
+                final PluginExecution pluginExecution = new PluginExecution();
+                pluginExecution.setId("site");
+                pluginExecution.setGoals(List.of("site"));
+                sitePlugin.setExecutions(List.of(pluginExecution));
+
+                pluginExecution.setConfiguration(siteConfig);
+
+                build.addPlugin(sitePlugin);
+            }
+
+            model.setBuild(build);
+            return project;
+        }
+    }
+
     private MavenProject fakeProject() {
         return fakeProject(null);
     }
 
     private MavenProject fakeProject(Map<String, String> properties) {
-        MavenProject project;
+        final Model model = new Model();
+
         if (properties != null) {
-            final Model model = new Model();
             model.getProperties().putAll(properties);
-            project = new MavenProject(model);
-        } else {
-            project = new MavenProject();
         }
+
+        final MavenProject project = new MavenProject(model);
+        final Plugin sitePlugin = new Plugin();
+        sitePlugin.setGroupId("org.apache.maven.plugins");
+        sitePlugin.setArtifactId("maven-site-plugin");
+        final PluginExecution pluginExecution = new PluginExecution();
+        pluginExecution.setId("site");
+        pluginExecution.setGoals(List.of("site"));
+        sitePlugin.setExecutions(List.of(pluginExecution));
+        final Build build = new Build();
+        build.addPlugin(sitePlugin);
+        model.setBuild(build);
+
+
         project.setFile(new File(".").getAbsoluteFile());
+
         return project;
     }
 
diff --git a/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/process/CopyResourcesProcessorTest.java b/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/process/CopyResourcesProcessorTest.java
index fc6376a..6a06696 100644
--- a/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/process/CopyResourcesProcessorTest.java
+++ b/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/process/CopyResourcesProcessorTest.java
@@ -31,7 +31,7 @@ public class CopyResourcesProcessorTest {
     @Test
     void should_not_fail_when_source_does_not_exist() {
         File nonExistentDir = new File(sourceDir, UUID.randomUUID().toString());
-        AsciidoctorMojo config = new AsciidoctorMojo();
+        AsciidoctorMojo config = createMojo();
 
         resourceProcessor.process(nonExistentDir, outputDir, config);
 
@@ -43,7 +43,7 @@ public class CopyResourcesProcessorTest {
     @Test
     void should_not_fail_when_output_does_not_exist() {
         File nonExistentDir = new File(sourceDir, UUID.randomUUID().toString());
-        AsciidoctorMojo config = new AsciidoctorMojo();
+        AsciidoctorMojo config = createMojo();
 
         resourceProcessor.process(sourceDir, nonExistentDir, config);
 
@@ -58,7 +58,7 @@ public class CopyResourcesProcessorTest {
         for (String fileExtension : fileExtensions)
             createFileWithContent(sourceDir, "source." + fileExtension);
 
-        resourceProcessor.process(sourceDir, outputDir, new AsciidoctorMojo());
+        resourceProcessor.process(sourceDir, outputDir, createMojo());
 
         assertThat(sourceDir.listFiles()).hasSize(fileExtensions.length);
         assertThat(outputDir.listFiles()).hasSize(0);
@@ -73,7 +73,7 @@ public class CopyResourcesProcessorTest {
                 createFileWithContent(sourceDir, filename);
         }
 
-        resourceProcessor.process(sourceDir, outputDir, new AsciidoctorMojo());
+        resourceProcessor.process(sourceDir, outputDir, createMojo());
 
         assertThat(sourceDir.listFiles()).hasSize(IGNORED_FILE_NAMES.length);
         assertThat(outputDir.listFiles()).hasSize(0);
@@ -82,7 +82,7 @@ public class CopyResourcesProcessorTest {
     @Test
     void should_ignore_sourceDocumentName() {
         final String sourceDocumentName = "my-file.special";
-        AsciidoctorMojo config = new AsciidoctorMojo();
+        AsciidoctorMojo config = createMojo();
         config.setSourceDocumentName(sourceDocumentName);
 
         createFileWithContent(sourceDir, sourceDocumentName);
@@ -95,7 +95,7 @@ public class CopyResourcesProcessorTest {
     @Test
     void should_ignore_sourceDocumentExtensions() {
         final List<String> fileExtensions = Arrays.asList("ext1", "ext2", "exta", "extb");
-        AsciidoctorMojo config = new AsciidoctorMojo();
+        AsciidoctorMojo config = createMojo();
         config.setSourceDocumentExtensions(fileExtensions);
 
         for (String fileExtension : fileExtensions)
@@ -111,7 +111,7 @@ public class CopyResourcesProcessorTest {
         createFileWithContent(sourceDir, "image.jpg");
         createFileWithContent(sourceDir, "image.gif");
 
-        resourceProcessor.process(sourceDir, outputDir, new AsciidoctorMojo());
+        resourceProcessor.process(sourceDir, outputDir, createMojo());
 
         assertThat(outputDir.list())
                 .containsExactlyInAnyOrder("image.jpg", "image.gif");
@@ -123,7 +123,7 @@ public class CopyResourcesProcessorTest {
         FileUtils.forceMkdir(new File(sourceDir, "sub_2"));
         FileUtils.forceMkdir(new File(sourceDir, "sub_1/sub_1_2"));
 
-        resourceProcessor.process(sourceDir, outputDir, new AsciidoctorMojo());
+        resourceProcessor.process(sourceDir, outputDir, createMojo());
 
         assertThat(outputDir.list())
                 .hasSize(0);
@@ -137,7 +137,7 @@ public class CopyResourcesProcessorTest {
             createFileWithContent(sourceDir, "image.jpg");
             createFileWithContent(sourceDir, "image.gif");
 
-            resourceProcessor.process(sourceDir, outputDir, new AsciidoctorMojo());
+            resourceProcessor.process(sourceDir, outputDir, createMojo());
 
             assertThat(outputDir.list())
                     .containsExactlyInAnyOrder("image.jpg", "image.gif");
@@ -149,7 +149,7 @@ public class CopyResourcesProcessorTest {
             createFileWithContent(new File(sourceDir, "sub_2"), "image.gif");
             createFileWithContent(new File(sourceDir, "sub_1/sub_1_2"), "image.bmp");
 
-            resourceProcessor.process(sourceDir, outputDir, new AsciidoctorMojo());
+            resourceProcessor.process(sourceDir, outputDir, createMojo());
 
             assertThat(outputDir.list())
                     .containsExactlyInAnyOrder("sub_1", "sub_2");
@@ -171,7 +171,7 @@ public class CopyResourcesProcessorTest {
                     .directory(sourceDir.getAbsolutePath())
                     .includes("**/*.txt")
                     .build();
-            AsciidoctorMojo configuration = new AsciidoctorMojo();
+            AsciidoctorMojo configuration = createMojo();
             configuration.setResources(Arrays.asList(resource));
 
             createFileWithContent(new File(sourceDir, "sub_1"), "image-1.txt");
@@ -198,7 +198,7 @@ public class CopyResourcesProcessorTest {
                     .directory(sourceDir.getAbsolutePath())
                     .includes("**/*.txt", "**/*.doc")
                     .build();
-            AsciidoctorMojo configuration = new AsciidoctorMojo();
+            AsciidoctorMojo configuration = createMojo();
             configuration.setResources(Arrays.asList(resource));
 
             createFileWithContent(new File(sourceDir, "sub_1"), "image-1.txt");
@@ -235,7 +235,7 @@ public class CopyResourcesProcessorTest {
                     .directory(sourceDir.getAbsolutePath())
                     .includes("**/*.img")
                     .build();
-            AsciidoctorMojo configuration = new AsciidoctorMojo();
+            AsciidoctorMojo configuration = createMojo();
             configuration.setResources(Arrays.asList(resource1, resource2));
 
             createFileWithContent(new File(sourceDir, "sub_1"), "image-1.txt");
@@ -268,7 +268,7 @@ public class CopyResourcesProcessorTest {
                     .directory(sourceDir.getAbsolutePath())
                     .excludes("**/*.img")
                     .build();
-            AsciidoctorMojo configuration = new AsciidoctorMojo();
+            AsciidoctorMojo configuration = createMojo();
             configuration.setResources(Arrays.asList(resource));
 
             createFileWithContent(new File(sourceDir, "sub_1"), "image-1.img");
@@ -298,7 +298,7 @@ public class CopyResourcesProcessorTest {
                     .targetPath(targetPath)
                     .excludes("**/*.img")
                     .build();
-            AsciidoctorMojo configuration = new AsciidoctorMojo();
+            AsciidoctorMojo configuration = createMojo();
             configuration.setResources(Arrays.asList(resource));
 
             createFileWithContent(new File(sourceDir, "sub_1"), "image-1.txt");
@@ -334,7 +334,7 @@ public class CopyResourcesProcessorTest {
                     .targetPath(targetPath)
                     .excludes("**/*.img")
                     .build();
-            AsciidoctorMojo configuration = new AsciidoctorMojo();
+            AsciidoctorMojo configuration = createMojo();
             configuration.setResources(Arrays.asList(resource));
 
             createFileWithContent(new File(sourceDir, "sub_1"), "image-1.txt");
@@ -365,7 +365,7 @@ public class CopyResourcesProcessorTest {
         @Test
         void should_not_copy_any_resource() {
             List<Resource> resources = ResourceBuilder.excludeAll();
-            AsciidoctorMojo configuration = new AsciidoctorMojo();
+            AsciidoctorMojo configuration = createMojo();
             configuration.setResources(resources);
 
             createFileWithContent(new File(sourceDir, "sub_1"), "image-1.txt");
@@ -383,4 +383,8 @@ public class CopyResourcesProcessorTest {
             assertThat(outputDir.list()).hasSize(0);
         }
     }
+
+    private static AsciidoctorMojo createMojo() {
+        return new AsciidoctorMojo(null, null, null,null);
+    }
 }
diff --git a/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/refresh/ResourceCopyFileAlterationListenerAdaptorTest.java b/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/refresh/ResourceCopyFileAlterationListenerAdaptorTest.java
index 03c07fb..d91052e 100644
--- a/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/refresh/ResourceCopyFileAlterationListenerAdaptorTest.java
+++ b/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/refresh/ResourceCopyFileAlterationListenerAdaptorTest.java
@@ -33,7 +33,7 @@ class ResourceCopyFileAlterationListenerAdaptorTest {
         final File srcDir = newOutputTestDirectory(TEST_DIR);
         final File outputDir = newOutputTestDirectory(TEST_DIR);
 
-        final AsciidoctorRefreshMojo mojo = new AsciidoctorRefreshMojo();
+        final AsciidoctorRefreshMojo mojo = createRefreshMojo();
         final Log logSpy = Mockito.spy(Log.class);
         final ResourceCopyFileAlterationListenerAdaptor listenerAdaptor
                 = new ResourceCopyFileAlterationListenerAdaptor(mojo, EMPTY_RUNNABLE, logSpy);
@@ -72,7 +72,7 @@ class ResourceCopyFileAlterationListenerAdaptorTest {
         final File srcDir = newOutputTestDirectory(TEST_DIR);
         final File outputDir = newOutputTestDirectory(TEST_DIR);
 
-        final AsciidoctorRefreshMojo mojo = new AsciidoctorRefreshMojo();
+        final AsciidoctorRefreshMojo mojo = createRefreshMojo();
         final Log logSpy = Mockito.spy(Log.class);
         final ResourceCopyFileAlterationListenerAdaptor listenerAdaptor
                 = new ResourceCopyFileAlterationListenerAdaptor(mojo, EMPTY_RUNNABLE, logSpy);
@@ -111,7 +111,7 @@ class ResourceCopyFileAlterationListenerAdaptorTest {
         final File srcDir = newOutputTestDirectory(TEST_DIR);
         final File outputDir = newOutputTestDirectory(TEST_DIR);
 
-        final AsciidoctorRefreshMojo mojo = new AsciidoctorRefreshMojo();
+        final AsciidoctorRefreshMojo mojo = createRefreshMojo();
         final Log logSpy = Mockito.spy(Log.class);
         final ResourceCopyFileAlterationListenerAdaptor listenerAdaptor
                 = new ResourceCopyFileAlterationListenerAdaptor(mojo, EMPTY_RUNNABLE, logSpy);
@@ -152,7 +152,7 @@ class ResourceCopyFileAlterationListenerAdaptorTest {
         final File srcDir = newOutputTestDirectory(TEST_DIR);
         final File outputDir = newOutputTestDirectory(TEST_DIR);
 
-        final AsciidoctorRefreshMojo mojo = new AsciidoctorRefreshMojo();
+        final AsciidoctorRefreshMojo mojo = createRefreshMojo();
         final Log logSpy = Mockito.spy(Log.class);
         final ResourceCopyFileAlterationListenerAdaptor listenerAdaptor
                 = new ResourceCopyFileAlterationListenerAdaptor(mojo, EMPTY_RUNNABLE, logSpy);
@@ -186,4 +186,8 @@ class ResourceCopyFileAlterationListenerAdaptorTest {
             assertThat(outputCandidate).exists();
         });
     }
+
+    private static AsciidoctorRefreshMojo createRefreshMojo() {
+        return new AsciidoctorRefreshMojo(null, null, null, null);
+    }
 }
diff --git a/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/test/MojoMocker.java b/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/test/MojoMocker.java
index 4755c0e..e7dd504 100644
--- a/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/test/MojoMocker.java
+++ b/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/test/MojoMocker.java
@@ -7,8 +7,12 @@ import java.util.Properties;
 import lombok.SneakyThrows;
 import org.apache.maven.plugin.logging.SystemStreamLog;
 import org.apache.maven.project.MavenProject;
+import org.asciidoctor.maven.AsciidoctorJFactory;
 import org.asciidoctor.maven.AsciidoctorMojo;
+import org.asciidoctor.maven.AsciidoctorOptionsFactory;
 import org.asciidoctor.maven.log.LogHandler;
+import org.asciidoctor.maven.process.CopyResourcesProcessor;
+import org.asciidoctor.maven.process.SourceDocumentFinder;
 import org.mockito.Mockito;
 
 import static org.codehaus.plexus.util.ReflectionUtils.setVariableValueInObject;
@@ -22,11 +26,17 @@ class MojoMocker {
     @SuppressWarnings("unchecked")
     <T> T mock(Class<T> clazz, Map<String, String> mavenProperties, LogHandler logHandler) {
 
-        final AsciidoctorMojo mojo = (AsciidoctorMojo) clazz.getConstructor(new Class[]{}).newInstance();
+        final AsciidoctorMojo mojo = (AsciidoctorMojo) clazz.getConstructors()[0].newInstance(new Object[]{
+            new AsciidoctorJFactory(),
+            new AsciidoctorOptionsFactory(),
+            new SourceDocumentFinder(),
+            new CopyResourcesProcessor()
+        });
 
         parametersInitializer.initialize(mojo);
         setVariableValueInObject(mojo, "log", new SystemStreamLog());
         setVariableValueInObject(mojo, "project", mockMavenProject(mavenProperties));
+
         if (logHandler != null)
             setVariableValueInObject(mojo, "logHandler", logHandler);
 
diff --git a/asciidoctor-parser-doxia-module/src/test/java/org/asciidoctor/maven/site/parser/AsciidoctorAstDoxiaParserTest.java b/asciidoctor-parser-doxia-module/src/test/java/org/asciidoctor/maven/site/parser/AsciidoctorAstDoxiaParserTest.java
index 6087f8d..cc13654 100644
--- a/asciidoctor-parser-doxia-module/src/test/java/org/asciidoctor/maven/site/parser/AsciidoctorAstDoxiaParserTest.java
+++ b/asciidoctor-parser-doxia-module/src/test/java/org/asciidoctor/maven/site/parser/AsciidoctorAstDoxiaParserTest.java
@@ -12,6 +12,9 @@ import org.apache.maven.doxia.parser.AbstractTextParser;
 import org.apache.maven.doxia.parser.ParseException;
 import org.apache.maven.doxia.sink.Sink;
 import org.apache.maven.project.MavenProject;
+import org.asciidoctor.maven.site.LogHandlerFactory;
+import org.asciidoctor.maven.site.SiteBaseDirResolver;
+import org.asciidoctor.maven.site.SiteConversionConfigurationParser;
 import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -24,7 +27,6 @@ import static org.asciidoctor.maven.site.parser.processors.test.StringTestUtils.
 import static org.asciidoctor.maven.site.parser.processors.test.TestNodeProcessorFactory.createSink;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.catchThrowable;
-import static org.codehaus.plexus.util.ReflectionUtils.setVariableValueInObject;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
@@ -228,29 +230,30 @@ class AsciidoctorAstDoxiaParserTest {
     }
 
     static class TestMocks {
+
         @SneakyThrows
-        static javax.inject.Provider<MavenProject> createMavenProjectMock(String configuration) {
+        static MavenProject createMockMavenProject(String configuration) {
             MavenProject mockProject = Mockito.mock(MavenProject.class);
             when(mockProject.getBasedir())
                 .thenReturn(new File("."));
             when(mockProject.getGoalConfiguration(anyString(), anyString(), anyString(), anyString()))
                 .thenReturn(configuration != null ? Xpp3DomBuilder.build(new StringReader(configuration)) : null);
 
-            return () -> mockProject;
+            return mockProject;
         }
 
         @SneakyThrows
         static AsciidoctorAstDoxiaParser mockAsciidoctorDoxiaParser() {
-            AsciidoctorAstDoxiaParser parser = new AsciidoctorAstDoxiaParser();
-            setVariableValueInObject(parser, "mavenProjectProvider", createMavenProjectMock(null));
             return mockAsciidoctorDoxiaParser(null);
         }
 
         @SneakyThrows
         static AsciidoctorAstDoxiaParser mockAsciidoctorDoxiaParser(String configuration) {
-            AsciidoctorAstDoxiaParser parser = new AsciidoctorAstDoxiaParser();
-            setVariableValueInObject(parser, "mavenProjectProvider", createMavenProjectMock(configuration));
-            return parser;
+            return new AsciidoctorAstDoxiaParser(
+                createMockMavenProject(configuration),
+                new SiteConversionConfigurationParser(new SiteBaseDirResolver()),
+                new LogHandlerFactory()
+            );
         }
     }
 }