diff --git a/.github/prompts/mental-model.prompt.md b/.github/prompts/mental-model.prompt.md new file mode 100644 index 000000000..a37288fe3 --- /dev/null +++ b/.github/prompts/mental-model.prompt.md @@ -0,0 +1,204 @@ +--- +description: Build Mental Model Documentation (Peter Naur) +--- + +Analyze this software project and create comprehensive mental model documentation following Peter Naur's "Programming as Theory Building" principles. Use arc42 as the structural framework and follow the docs-as-code approach according to Ralf D. Müller. you will find the arc42 template in the `docs/arc42` folder. Use plantUML C4 diagrams where applicable. + +## Your Task + +Build documentation that enables new senior developers to understand not just WHAT the system does, but WHY it exists and HOW to think about it. + +## Documentation Structure + +Create the following arc42-based structure: + +### arc42/01-introduction.md +- System vision and purpose +- Core quality goals (top 3) +- Stakeholders +- **ADD:** Technical roadmap (next 12 months) +- **ADD:** Deprecation plans + +### arc42/03-context.md +- System boundaries (what's IN vs OUT) +- External interfaces +- Neighbor systems with rationale + +### arc42/04-solution-strategy.md +⭐ **CRITICAL:** Document the top 5 architecture decisions +For each: Decision + Rationale + Consequences + Alternatives considered + +### arc42/05-building-blocks.md +- Component landscape +- For each component: Role, Responsibility, Key constraint +- Dependency relationships + +### arc42/08-concepts.md +⭐ **CRITICAL:** Mental Model Map + +Include: +1. **Core Metaphor:** Central analogy for the system +2. **Must-Understand Concepts:** 3-5 fundamental concepts with: + - Why it matters + - Impact on development + - Common mistakes newcomers make +3. **Unwritten Rules:** Team conventions not in code +4. **Failed Experiments:** "We tried X, didn't work because Y" +5. **Cross-cutting concerns:** Error handling, logging, security patterns + +### arc42/09-decisions/ +⭐ **CRITICAL:** Architecture Decision Records (ADRs) + +Follow Michael Nygard's ADR format with these extensions: + +**Required sections:** +- Status, Date, Context, Decision, Consequences +- **ADD:** Problem Statement (what issue are we solving?) +- **ADD:** Pugh Matrix for alternatives evaluation + +**Pugh Matrix format:** +``` +| Criterion | Baseline | Alt 1 | Alt 2 | +|-----------------|----------|-------|-------| +| [Criterion 1] | 0 | -2/+2 | -2/+2 | +| [Criterion 2] | 0 | -2/+2 | -2/+2 | +| Total Score | 0 | X | Y | +``` +Scale: -2 (much worse) to +2 (much better) vs baseline + +Write each ADR to its own file. + +**ADD:** README.md with ADR timeline showing evolution phases + +### arc42/11-risks.md +**ADD:** Post-mortems section +- Major incidents +- Root causes +- Lessons learned +- Why certain rules exist now + +### onboarding/journey-map.md +4-week learning path: +- Week 1: Overview (goals + validation questions) +- Week 2: Fundamentals (goals + validation questions) +- Week 3: Deep dive (goals + validation questions) +- Week 4: Independence (goals + validation questions) + +### onboarding/development-workflow.md +- Feature lifecycle (design → implement → review → deploy) +- Review checklist +- When to write ADRs +- Deployment process + +### onboarding/team-structure.md +- Knowledge map (who knows what) +- Code ownership +- Decision processes +- Escalation paths + +### onboarding/common-issues.md +Troubleshooting patterns in format: +**Symptom** → **Common Cause** → **Debugging Steps** → **Prevention** + +### llm/knowledge-graph.md +⭐ **FOR LLM USAGE:** Structured knowledge + +```yaml +concepts: + - name: [Concept Name] + level: [0=Fundamental, 1=Architecture, 2=Implementation] + priority: [CRITICAL/HIGH/MEDIUM/LOW] + prerequisites: [list] + enables: [list] + learning_time: [estimate] + common_mistakes: + - mistake: [description] + why: [root cause] + correct: [right approach] + validation: [question to verify understanding] + code_locations: [where to find examples] +``` + +### C4 diagrams + +Example: + +```plantuml +!include + +title System Context diagram for Internet Banking System + +Person(customer, "Banking Customer", "A customer of the bank, with personal bank accounts.") +System(banking_system, "Internet Banking System", "Allows customers to check their accounts.") + +System_Ext(mail_system, "E-mail system", "The internal Microsoft Exchange e-mail system.") +System_Ext(mainframe, "Mainframe Banking System", "Stores all of the core banking information.") + +Rel(customer, banking_system, "Uses") +Rel_Back(customer, mail_system, "Sends e-mails to") +Rel_Neighbor(banking_system, mail_system, "Sends e-mails", "SMTP") +Rel(banking_system, mainframe, "Uses") +``` + +### llm/antipatterns.md +Document what NOT to do: +``` +❌ Antipattern: [Name] +Why wrong: [explanation] +✅ Correct approach: [solution] +Code example: [side-by-side comparison] +Related: [ADR/concept links] +``` + +## Critical Requirements + +1. **Document the "Why"** - Not just what exists, but why decisions were made +2. **Make implicit explicit** - Capture "everyone knows" tribal knowledge +3. **Show evolution** - How the system got here (phases, pivots, migrations) +4. **Include failures** - What didn't work and lessons learned +5. **Hierarchy over timeline** - Structure concepts by dependency, not sequence +6. **Validate understanding** - Include questions to test comprehension +7. **Link everything** - Connect ADRs, code, concepts, runbooks + +## Open Questions Report + +**CRITICAL:** Create `open-questions.md` documenting: +- Missing information you need +- Ambiguities found in code/docs +- Unclear design decisions +- Assumptions you had to make +- Areas needing clarification from team +- Inconsistencies discovered + +Format: +```markdown +## [Category] +**Question:** [specific question] +**Context:** [why this matters] +**Impact:** [what's blocked without answer] +**Found in:** [file/location] +**Assumption made:** [if you proceeded anyway] +``` + +## Quality Criteria + +Documentation is complete when a new senior developer can: +- ✅ Understand design decisions without asking team +- ✅ Know why the system is built this way +- ✅ Develop features consistent with architecture +- ✅ Recognize violations of unwritten rules +- ✅ Debug issues using documented patterns +- ✅ Avoid common pitfalls that trapped others + +## Process + +1. Analyze codebase structure, dependencies, patterns +2. Review existing docs (if any) +3. Identify core architectural decisions +4. Extract implicit knowledge from code patterns +5. Document in arc42 structure in asciidoc format with plantuml diagrams +6. Build LLM knowledge graph +7. Create onboarding journey +8. **Document all open questions continuously** + +Focus on Peter Naur's insight: The real program is the theory in developers' minds, not the code itself. Your job is to externalize that theory. \ No newline at end of file diff --git a/README.asciidoc b/README.asciidoc index 5e5383d65..8f6a38030 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -19,6 +19,13 @@ image:https://badges.gitter.im/jbake-org/jbake.svg["Gitter Chat", link="https:// Full documentation is available on http://jbake.org/docs/[jbake.org]. +Architecture documentation (AI generated) is available in `/docs` and can be rendered as site using `./dtcw generateSite` command. +https://doctoolchain.org/[DocToolchain] is used to build the documentation site, which uses JBake under the hood. + +Rendered site is available at build/microsite/output/index.html after running the command. + +Another AI generated documentation is available at https://deepwiki.com/jbake-org/jbake + == Contributing We welcome all contributions to the project both big and small. From new features, bug reports to even spelling mistake corrections in diff --git a/docToolchainConfig.groovy b/docToolchainConfig.groovy new file mode 100644 index 000000000..21d5c6f9b --- /dev/null +++ b/docToolchainConfig.groovy @@ -0,0 +1,548 @@ +outputPath = 'build' + +// If you want to use the Antora integration, set this to true. +// This requires your project to be setup as Antora module. +// You can use `downloadTemplate` task to bootstrap your project. +//useAntoraIntegration = false + +// Path where the docToolchain will search for the input files. +// This path is appended to the docDir property specified in gradle.properties +// or in the command line, and therefore must be relative to it. + +inputPath = 'docs'; + +// if you need to register custom Asciidoctor extensions, this is the right place +// configure the name and path to your extension, relative to the root of your project +// (relative to dtcw). For example: 'src/ruby/asciidoctor-lists.rb'. +// this is the same as the `requires`-list of the asciidoctor gradle plugin. The extensions will be +// registered for generateDeck, generateHtml, generatePdf and generateDocbook tasks, only. +// rubyExtensions = [] + +// the pdfThemeDir config in this file is outdated. +// please check http://doctoolchain.org/docToolchain/v2.0.x/020_tutorial/030_generateHTML.html#_pdf_style for further details +// pdfThemeDir = './src/docs/pdfTheme' + +inputFiles = [ + //[file: 'doctoolchain_demo.adoc', formats: ['html','pdf']], + //[file: 'arc42-template.adoc', formats: ['html','pdf']], + [file: 'arc42/arc42.adoc', formats: ['html','pdf']], + /** inputFiles **/ +] + +//folders in which asciidoc will find images. +//these will be copied as resources to ./images +//folders are relative to inputPath +// Hint: If you define an imagepath in your documents like +// :imagesdir: ./whatsoever +// define it conditional like +// ifndef::imagesdir[:imagesdir: ./whatsoever] +// as doctoolchain defines :imagesdir: during generation +imageDirs = [ + 'images/.', + 'images/.', + /** imageDirs **/ +] + +// whether the build should fail when detecting broken image references +// if this config is set to true all images will be embedded +failOnMissingImages = true + +// these are directories (dirs) and files which Gradle monitors for a change +// in order to decide if the docs have to be re-build +taskInputsDirs = [ + "${inputPath}", +// "${inputPath}/src", +// "${inputPath}/images", + ] + +taskInputsFiles = [] + +//***************************************************************************************** + +// Configuration for customTasks +// create a new Task with ./dtcw createTask +customTasks = [ +/** customTasks **/ +] + + +//***************************************************************************************** + +//Configuration for microsite: generateSite + previewSite + +microsite = [:] + +// these properties will be set as jBake properties +// microsite.foo will be site.foo in jBake and can be used as config.site_foo in a template +// see https://jbake.org/docs/2.6.4/#configuration for how to configure jBake +// other properties listed here might be used in the jBake templates and thus are not +// documented in the jBake docs but hopefully in the template docs. +microsite.with { + /** start:microsite **/ + + // is your microsite deployed with a context path? + contextPath = '/' + // the folder of a site definition (theme) relative to the docDir+inputPath + //siteFolder = '../site' + + /** end:microsite **/ + + //project theme + //site folder relative to the docs folder + //see 'copyTheme' for more details + siteFolder = '../site' + + // the title of the microsite, displayed in the upper left corner + title = '##site-title##' + // the next items configure some links in the footer + // + // contact eMail + // example: mailto:bert@example.com + footerMail = '##footer-email##' + // + // twitter account url + footerTwitter = '##twitter-url##' + // + // Stackoverflow QA + footerSO = '##Stackoverflow-url##' + // + // Github Repository + footerGithub = '##Github-url##' + // + // Slack Channel + footerSlack = '##Slack-url##' + // + // Footer Text + // example: built with docToolchain and jBake
theme: docsy
+ footerText = 'built with docToolchain and jBake
theme: docsy
' + // + // site title if no other title is given + title = 'docToolchain' + // + // the url to create an issue in github + // Example: https://github.com/docToolchain/docToolchain/issues/new + issueUrl = '##issue-url##' + // + // the base url for code files in github + // Example: https://github.com/doctoolchain/doctoolchain/edit/master/src/docs + branch = System.getenv("DTC_PROJECT_BRANCH")?:'-' + gitRepoUrl = '##git-repo-url##' + + // + // the location of the landing page + landingPage = 'landingpage.gsp' + // the menu of the microsite. A map of [code:'title'] entries to specify the order and title of the entries. + // the codes are autogenerated from the folder names or :jbake-menu: attribute entries from the .adoc file headers + // set a title to '-' in order to remove this menu entry. + menu = [:] + +//tag::additionalConverters[] +/** + +if you need support for additional markup converters, you can configure them here +you have three different types of script you can define: + +- groovy: just groovy code as string +- groovyFile: path to a groovy script +- bash: a bash command. It will receive the name of the file to be converted as first argument + +`groovy` and `groovyFile` will have access to the file and config object + +`dtcw:rstToHtml.py` is an internal script to convert restructuredText. +Needs `python3` and `docutils` installed. + +**/ + additionalConverters = [ + //'.one': [command: 'println "test"+file.canonicalPath', type: 'groovy'], + //'.two': [command: 'scripts/convert-md.groovy', type: 'groovyFile'], + //'.rst': [command: 'dtcw:rstToHtml.py', type: 'bash'], + ] +//end::additionalConverters[] + + // if you prefer another convention regarding the automatic generation + // of jBake headers, you can configure a script to modify them here + // the script has access to + // - file: the current object + // - sourceFolder: the copy of the docs-source on which the build operates + // default `/microsite/tmp/site/doc` + // - config: the config object (this file, but parsed) + // - headers: already parsed headers to be modified + /** + customConvention = """ + System.out.println file.canonicalPath + headers.title += " - from CustomConvention" + """.stripIndent() + **/ +} + +//***************************************************************************************** + +//Configuration for exportChangelog + +exportChangelog = [:] + +changelog.with { + + // Directory of which the exportChangelog task will export the changelog. + // It should be relative to the docDir directory provided in the + // gradle.properties file. + dir = 'src/docs' + + // Command used to fetch the list of changes. + // It should be a single command taking a directory as a parameter. + // You cannot use multiple commands with pipe between. + // This command will be executed in the directory specified by changelogDir + // it the environment inherited from the parent process. + // This command should produce asciidoc text directly. The exportChangelog + // task does not do any post-processing + // of the output of that command. + // + // See also https://git-scm.com/docs/pretty-formats + cmd = 'git log --pretty=format:%x7c%x20%ad%x20%n%x7c%x20%an%x20%n%x7c%x20%s%x20%n --date=short' + +} + +//***************************************************************************************** + +//tag::confluenceConfig[] +//Configuration for publishToConfluence + +confluence = [:] + +/** +//tag::input-config[] + +*input* + +is an array of files to upload to Confluence with the ability +to configure a different parent page for each file. + +=== Attributes + +- `file`: absolute or relative path to the asciidoc generated html file to be exported +- `url`: absolute URL to an asciidoc generated html file to be exported +- `ancestorName` (optional): the name of the parent page in Confluence as string; + this attribute has priority over ancestorId, but if page with given name doesn't exist, + ancestorId will be used as a fallback +- `ancestorId` (optional): the id of the parent page in Confluence as string; leave this empty + if a new parent shall be created in the space + +The following four keys can also be used in the global section below + +- `spaceKey`: page specific variable for the key of the confluence space to write to + case sensitive! If the case is not correct, it can be that new page will be + created but can't be updated in the next run. +- `subpagesForSections` (optional): The number of nested sub-pages to create. Default is '1'. + '0' means creating all on one page. + The following migration for removed configuration can be used. +** `allInOnePage = true` is the same as `subpagesForSections = 0` +** `allInOnePage = false && createSubpages = false` is the same as `subpagesForSections = 1` +** `allInOnePage = false && createSubpages = true` is the same as `subpagesForSections = 2` +- `pagePrefix` (optional): page specific variable, the pagePrefix will be a prefix for the page title and it's sub-pages + use this if you only have access to one confluence space but need to store several + pages with the same title - a different pagePrefix will make them unique +- `pageSuffix` (optional): same usage as prefix but appended to the title and it's subpages + +only 'file' or 'url' is allowed. If both are given, 'url' is ignored + +//end::input-config[] +**/ + +confluence.with { + input = [ + [ file: "build/html5/arc42-template-de.html" ], + ] + + // endpoint of the confluenceAPI (REST) to be used + // if you use Confluence Cloud, you can set this value to + // https://[yourServer] + // a working example is https://arc42-template.atlassian.net + // if you use Confluence Server, you may need to set a context: + // https://[yourServer]/[context] + // a working example is https://arc42-template.atlassian.net/wiki + api = 'https://[yourServer]/[context]' + + // requests per second for confluence API calls + rateLimit = 10 + + // if true API V1 only will be used. Default is true. + // useV1Api = true + + // if true, the new editor v2 will be used. Default is false. + // enforceNewEditor = false + + // Additionally, spaceKey, subpagesForSections, pagePrefix and pageSuffix can be globally defined here. The assignment in the input array has precedence + + // the key of the confluence space to write to + spaceKey = 'asciidoc' + + // if true, all pages will be created using the new editor v2 + // enforceNewEditor = false + + // variable to determine how many layers of sub pages should be created + subpagesForSections = 1 + + // the pagePrefix will be a prefix for each page title + // use this if you only have access to one confluence space but need to store several + // pages with the same title - a different pagePrefix will make them unique + pagePrefix = '' + + pageSuffix = '' + + // the comment used for the page version + pageVersionComment = '' + + /* + WARNING: It is strongly recommended to store credentials securely instead of commiting plain text values to your git repository!!! + + Tool expects credentials that belong to an account which has the right permissions to to create and edit confluence pages in the given space. + Credentials can be used in a form of: + - passed parameters when calling script (-PconfluenceUser=myUsername -PconfluencePass=myPassword) which can be fetched as a secrets on CI/CD or + - gradle variables set through gradle properties (uses the 'confluenceUser' and 'confluencePass' keys) + Often, same credentials are used for Jira & Confluence, in which case it is recommended to pass CLI parameters for both entities as + -Pusername=myUser -Ppassword=myPassword + */ + + //optional API-token to be added in case the credentials are needed for user and password exchange. + //apikey = "[API-token]" + + // HTML Content that will be included with every page published + // directly after the TOC. If left empty no additional content will be + // added + // extraPageContent = 'This is a generated page, do not edit! + extraPageContent = '' + + // enable or disable attachment uploads for local file references + enableAttachments = false + + // variable to limit number of pages retreived per REST-API call + pageLimit = 100 + + // default attachmentPrefix = attachment - All files to attach will require to be linked inside the document. + // attachmentPrefix = "attachment" + + + // Optional proxy configuration, only used to access Confluence + // schema supports http and https + // proxy = [host: 'my.proxy.com', port: 1234, schema: 'http'] + + // Optional: specify which Confluence OpenAPI Macro should be used to render OpenAPI definitions + // possible values: ["confluence-open-api", "open-api", true]. true is the same as "confluence-open-api" for backward compatibility + // useOpenapiMacro = "confluence-open-api" + + // for exportConfluence-Task + export = [ + srcDir: 'sample_data', + destDir: 'src/docs' + ] + +} +//end::confluenceConfig[] + +//***************************************************************************************** +//tag::exportEAConfig[] +//Configuration for the export script 'exportEA.vbs'. +// The following parameters can be used to change the default behaviour of 'exportEA'. +// All parameter are optionally. +// Parameter 'connection' allows to select a certain database connection by using the ConnectionString as used for +// directly connecting to the project database instead of looking for EAP/EAPX files inside and below the 'src' folder. +// Parameter 'packageFilter' is an array of package GUID's to be used for export. All images inside and in all packages below the package represented by its GUID are exported. +// A packageGUID, that is not found in the currently opened project, is silently skipped. +// PackageGUID of multiple project files can be mixed in case multiple projects have to be opened. + +exportEA.with { +// OPTIONAL: Set the connection to a certain project or comment it out to use all project files inside the src folder or its child folder. +// connection = "DBType=1;Connect=Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=[THE_DB_NAME_OF_THE_PROJECT];Data Source=[server_hosting_database.com];LazyLoad=1;" +// OPTIONAL: Add one or multiple packageGUIDs to be used for export. All packages are analysed, if no packageFilter is set. +// packageFilter = [ +// "{A237ECDE-5419-4d47-AECC-B836999E7AE0}", +// "{B73FA2FB-267D-4bcd-3D37-5014AD8806D6}" +// ] +// OPTIONAL: relative path to base 'docDir' to which the diagrams and notes are to be exported +// exportPath = "src/docs/" +// OPTIONAL: relative path to base 'docDir', in which Enterprise Architect project files are searched +// searchPath = "src/docs/" + +} +//end::exportEAConfig[] + +//tag::htmlSanityCheckConfig[] +htmlSanityCheck.with { + //sourceDir = "build/html5/site" + //checkingResultsDir = +} +//end::htmlSanityCheckConfig[] + +//tag::jiraConfig[] +// Configuration for Jira related tasks +jira = [:] + +jira.with { + + // endpoint of the JiraAPI (REST) to be used + api = 'https://your-jira-instance' + + // requests per second for jira API calls + rateLimit = 10 + + /* + WARNING: It is strongly recommended to store credentials securely instead of commiting plain text values to your git repository!!! + + Tool expects credentials that belong to an account which has the right permissions to read the JIRA issues for a given project. + Credentials can be used in a form of: + - passed parameters when calling script (-PjiraUser=myUsername -PjiraPass=myPassword) which can be fetched as a secrets on CI/CD or + - gradle variables set through gradle properties (uses the 'jiraUser' and 'jiraPass' keys) + Often, Jira & Confluence credentials are the same, in which case it is recommended to pass CLI parameters for both entities as + -Pusername=myUser -Ppassword=myPassword + */ + + // the key of the Jira project + project = 'PROJECTKEY' + + // the format of the received date time values to parse + dateTimeFormatParse = "yyyy-MM-dd'T'H:m:s.SSSz" // i.e. 2020-07-24'T'9:12:40.999 CEST + + // the format in which the date time should be saved to output + dateTimeFormatOutput = "dd.MM.yyyy HH:mm:ss z" // i.e. 24.07.2020 09:02:40 CEST + + // the label to restrict search to + label = + + // Legacy settings for Jira query. This setting is deprecated & support for it will soon be completely removed. Please use JiraRequests settings + //jql = "project='%jiraProject%' AND labels='%jiraLabel%' ORDER BY priority DESC, duedate ASC" + + // Base filename in which Jira query results should be stored + resultsFilename = 'JiraTicketsContent' + + saveAsciidoc = true // if true, asciidoc file will be created with *.adoc extension + saveExcel = true // if true, Excel file will be created with *.xlsx extension + + // Output folder for this task inside main outputPath + resultsFolder = 'JiraRequests' + + /* + List of requests to Jira API: + These are basically JQL expressions bundled with a filename in which results will be saved. + User can configure custom fields IDs and name those for column header, + i.e. customfield_10026:'Story Points' for Jira instance that has custom field with that name and will be saved in a coloumn named "Story Points" + */ + exports = [ + [ + filename:"File1_Done_issues", + jql:"project='%jiraProject%' AND status='Done' ORDER BY duedate ASC", + customfields: [customfield_10026:'Story Points'] + ], + [ + filename:'CurrentSprint', + jql:"project='%jiraProject%' AND Sprint in openSprints() ORDER BY priority DESC, duedate ASC", + customfields: [customfield_10026:'Story Points'] + ], + ] +} +//end::jiraConfig[] + +//tag::openApiConfig[] +// Configuration for OpenAPI related task +openApi = [:] + +// 'specFile' is the name of OpenAPI specification yaml file. Tool expects this file inside working dir (as a filename or relative path with filename) +// 'infoUrl' and 'infoEmail' are specification metadata about further info related to the API. By default this values would be filled by openapi-generator plugin placeholders +// + +openApi.with { + specFile = 'src/docs/petstore-v2.0.yaml' // i.e. 'petstore.yaml', 'src/doc/petstore.yaml' + infoUrl = 'https://my-api.company.com' + infoEmail = 'info@company.com' +} +//end::openApiConfig[] + +//tag::sprintChangelogConfig[] +// Sprint changelog configuration generate changelog lists based on tickets in sprints of an Jira instance. +// This feature requires at least Jira API & credentials to be properly set in Jira section of this configuration +sprintChangelog = [:] +sprintChangelog.with { + sprintState = 'closed' // it is possible to define multiple states, i.e. 'closed, active, future' + ticketStatus = "Done, Closed" // it is possible to define multiple ticket statuses, i.e. "Done, Closed, 'in Progress'" + + showAssignee = false + showTicketStatus = false + showTicketType = true + sprintBoardId = 12345 // Jira instance probably have multiple boards; here it can be defined which board should be used + + // Output folder for this task inside main outputPath + resultsFolder = 'Sprints' + + // if sprintName is not defined or sprint with that name isn't found, release notes will be created on for all sprints that match sprint state configuration + sprintName = 'PRJ Sprint 1' // if sprint with a given sprintName is found, release notes will be created just for that sprint + allSprintsFilename = 'Sprints_Changelogs' // Extension will be automatically added. +} +//end::sprintChangelogConfig[] + +//tag::collectIncludesConfig[] +collectIncludes = [:] + +collectIncludes.with { + + fileFilter = "adoc" // define which files are considered. default: "ad|adoc|asciidoc" + + minPrefixLength = "3" // define what minimum length the prefix. default: "3" + + maxPrefixLength = "3" // define what maximum length the prefix. default: "" + + separatorChar = "_" // define the allowed separators after prefix. default: "-_" + + cleanOutputFolder = true // should the output folder be emptied before generation? default: false + + excludeDirectories = [] // define additional directories that should not be traversed. + +} +//end::collectIncludesConfig[] + +//tag::structurizrConfig[] +// Configuration for Structurizr related tasks +structurizr = [:] + +structurizr.with { + + // Configure where `exportStructurizr` looks for the Structurizr model. + workspace = { + // The directory in which the Structurizr workspace file is located. + // path = 'src/docs/structurizr' + + // By default `exportStructurizr` looks for a file '${structurizr.workspace.path}/workspace.dsl'. + // You can customize this behavior with 'filename'. Note that the workspace filename is provided without '.dsl' extension. + // filename = 'workspace' + } + + export = { + // Directory for the exported diagrams. + // + // WARNING: Do not put manually created/changed files into this directory. + // If a valid Structurizr workspace file is found the directory is deleted before the diagram files are generated. + // outputPath = 'src/docs/structurizr/diagrams' + + // Format of the exported diagrams. Defaults to 'plantuml' if the parameter is not provided. + // + // Following formats are supported: + // - 'plantuml': the same as 'plantuml/structurizr' + // - 'plantuml/structurizr': exports views to PlantUML + // - 'plantuml/c4plantuml': exports views to PlantUML with https://github.com/plantuml-stdlib/C4-PlantUML + // format = 'plantuml' + } +} +//end::structurizrConfig[] + +//tag::openAIConfig[] +// Configuration for openAI related tasks +openAI = [:] + +openAI.with { + // This task requires a person access token for openAI. + // Ensure to pass this token as parameters when calling the task + // using -PopenAI.token=xx-xxxxxxxxxxxxxx + + //model = "text-davinci-003" + //maxToken = '500' + //temperature = '0.3' +} +//end::openAIConfig[] diff --git a/docs/arc42/.asciidoctorconfig.adoc b/docs/arc42/.asciidoctorconfig.adoc new file mode 100644 index 000000000..e0bfb9168 --- /dev/null +++ b/docs/arc42/.asciidoctorconfig.adoc @@ -0,0 +1,2 @@ +:imagesdir: ../images + diff --git a/docs/arc42/arc42.adoc b/docs/arc42/arc42.adoc new file mode 100644 index 000000000..3b09ee34f --- /dev/null +++ b/docs/arc42/arc42.adoc @@ -0,0 +1,90 @@ +:imagesdir: ../images +:jbake-menu: - +// header file for arc42-template, +// including all help texts +// +// ==================================== + +// configure EN settings for asciidoc +include::chapters/config.adoc[] + += image:arc42-logo.png[arc42] Template +:revnumber: {revnumber} +:revdate: {revdate} +:revremark: {revremark} +:toc-title: Table of Contents + +//additional style for arc42 help callouts +include::../../common/styles/arc42-help-style.adoc[] + + + +include::chapters/about-arc42.adoc[] + +// horizontal line +*** + +ifdef::arc42help[] +[role="arc42help"] +**** +[NOTE] +==== +This version of the template contains some help and explanations. +It is used for familiarization with arc42 and the understanding of the concepts. +For documentation of your own system you use better the _plain_ version. +==== +**** +endif::arc42help[] + +// numbering from here on +:numbered: + +<<<< +// 1. Introduction and Goals +include::chapters/01_introduction_and_goals.adoc[] + +<<<< +// 2. Architecture Constraints +include::chapters/02_architecture_constraints.adoc[] + +<<<< +// 3. Context and Scope +include::chapters/03_context_and_scope.adoc[] + +<<<< +// 4. Solution Strategy +include::chapters/04_solution_strategy.adoc[] + +<<<< +// 5. Building Block View +include::chapters/05_building_block_view.adoc[] + +<<<< +// 6. Runtime View +include::chapters/06_runtime_view.adoc[] + +<<<< +// 7. Deployment View +include::chapters/07_deployment_view.adoc[] + +<<<< +// 8. Concepts +include::chapters/08_concepts.adoc[] + +<<<< +// 9. Architecture Decisions +include::chapters/09_architecture_decisions.adoc[] + +<<<< +// 10. Quality Requirements +include::chapters/10_quality_requirements.adoc[] + +<<<< +// 11. Technical Risks +include::chapters/11_technical_risks.adoc[] + +<<<< +// 12. Glossary +include::chapters/12_glossary.adoc[] + + diff --git a/docs/arc42/chapters/.asciidoctorconfig.adoc b/docs/arc42/chapters/.asciidoctorconfig.adoc new file mode 100644 index 000000000..f51344152 --- /dev/null +++ b/docs/arc42/chapters/.asciidoctorconfig.adoc @@ -0,0 +1,2 @@ +:imagesdir: ../../images + diff --git a/docs/arc42/chapters/01_introduction_and_goals.adoc b/docs/arc42/chapters/01_introduction_and_goals.adoc new file mode 100644 index 000000000..6db0e67d3 --- /dev/null +++ b/docs/arc42/chapters/01_introduction_and_goals.adoc @@ -0,0 +1,156 @@ +:jbake-title: Introduction and Goals +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 1 +:filename: /chapters/01_introduction_and_goals.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + +[[section-introduction-and-goals]] +== Introduction and Goals + +=== System Vision and Purpose + +JBake is a Java-based static site generator designed for developers who want to "bake" websites from content written in markup languages. The core metaphor: *JBake is like an oven that transforms raw ingredients (markup content, templates, assets) into a fully-baked website (HTML files)*. + +**Primary Purpose:** + +* Convert markup documents (Markdown, AsciiDoc, HTML) into static HTML websites +* Provide a blog-aware generator with support for posts, pages, tags, and archives +* Enable template-driven rendering with multiple template engine options +* Support developer workflows with live preview and incremental building + +**Key Business Goals:** + +1. **Developer Productivity**: Enable rapid website creation without complex frameworks +2. **Flexibility**: Support multiple markup languages and template engines +3. **Simplicity**: Provide "convention over configuration" approach with sensible defaults +4. **Extensibility**: Allow plugins and customization through Java SPI mechanism + +=== Requirements Overview + +**Essential Features:** + +[cols="1,3"] +|=== +|Feature |Description + +|Content Processing +|Parse and convert multiple markup formats (Markdown, AsciiDoc, HTML) to HTML + +|Template Rendering +|Support multiple template engines (Freemarker, Groovy, Thymeleaf, Pebble, Jade) + +|Blog Support +|Generate blog structures with posts, pages, archives, tags, and RSS feeds + +|Asset Management +|Copy static assets and handle asset folders + +|Incremental Builds +|Detect changed files and rebuild only what's necessary + +|Live Preview +|Built-in Jetty server for local preview during development + +|Caching +|OrientDB-based content cache for performance optimization + +|Data Files +|Support external data files (JSON, YAML) for template injection +|=== + +**Target Users:** + +* Developers creating personal blogs or documentation sites +* Teams building static websites with version-controlled content +* Technical writers using AsciiDoc or Markdown +* Users migrating from other static site generators + +=== Requirements Overview + +**Core Requirements:** + +1. **Content Crawling**: Recursively scan content folder and parse markup files +2. **Incremental Processing**: SHA1-based change detection to avoid redundant processing +3. **Template Selection**: Automatic template matching based on document type +4. **Output Generation**: Write rendered HTML to destination folder +5. **Asset Handling**: Copy static files (CSS, JS, images) to output +6. **Preview Server**: Serve generated site locally for development + +=== Quality Goals + +[options="header",cols="1,2,3"] +|=== +|Priority |Quality Goal |Motivation & Scenarios + +|1 +|**Extensibility** +|Support multiple markup and template engines without modifying core code. New engines can be added via Java SPI mechanism. *Scenario*: A developer wants to add support for a new template engine - they implement AbstractTemplateEngine, register via META-INF/services, and JBake auto-discovers it. + +|2 +|**Performance** +|Process large sites efficiently through caching and incremental builds. *Scenario*: When rebuilding a 1000-page site, only changed files are reprocessed. OrientDB cache stores parsed content with SHA1 hashing for change detection. + +|3 +|**Simplicity** +|Provide zero-configuration startup with sensible defaults. *Scenario*: Running `jbake -i -b` on an empty folder creates example project structure and generates the site immediately without requiring configuration files. + +|4 +|**Modularity** +|Clear separation between parsing, rendering, and output stages. *Scenario*: Template engine failures don't crash the crawler; each rendering tool handles its own errors independently. + +|5 +|**Compatibility** +|Maintain backwards compatibility with existing projects. *Scenario*: Deprecated constructors and methods are kept with @Deprecated annotations, allowing gradual migration to new APIs while supporting legacy code. +|=== + +=== Stakeholders + +[options="header",cols="1,2,3"] +|=== +|Role/Name |Contact |Expectations + +|**End Users** (Bloggers & Technical Writers) +|User community via mailing list, forum +|Easy-to-use tool for creating static sites; good documentation; support for popular markup formats; fast build times + +|**Contributors** (Core Developers) +|GitHub jbake-org/jbake +|Clean, maintainable architecture; clear extension points; comprehensive tests; backwards compatibility + +|**Plugin Developers** +|External developers +|Well-documented SPI interfaces; stable APIs for template engines, markup parsers, and rendering tools; minimal dependencies + +|**Embedding Projects** +|JBake Maven Plugin, Gradle Plugin users +|Programmatic API access; configuration flexibility; ability to run JBake embedded in build tools + +|**Site Maintainers** +|jbake.org documentation team +|Dogfooding: JBake documentation site uses JBake itself (with DocToolchain); need stable, reliable builds + +|**Java Ecosystem** +|Maven Central consumers +|Semantic versioning; proper dependency management; Java 8+ compatibility +|=== + +=== Technical Roadmap (Next 12 Months) + +**Planned Enhancements:** + +* Java 11+ as minimum version (migrate from Java 8) +* Improved incremental build performance +* Better error reporting with context +* Modern template engine support (consider removing legacy engines) +* Enhanced CLI with picocli improvements +* Migration guide for breaking changes + +**Deprecation Plans:** + +* Legacy constructor patterns with File/CompositeConfiguration parameters → JBakeConfiguration-based constructors +* Direct OrientDB API exposure → Abstract ContentStore methods +* Mutable configuration via setters → Immutable configuration builders diff --git a/docs/arc42/chapters/02_architecture_constraints.adoc b/docs/arc42/chapters/02_architecture_constraints.adoc new file mode 100644 index 000000000..4853303a3 --- /dev/null +++ b/docs/arc42/chapters/02_architecture_constraints.adoc @@ -0,0 +1,39 @@ +:jbake-title: Architecture Constraints +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 2 +:filename: /chapters/02_architecture_constraints.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-architecture-constraints]] +== Architecture Constraints + + +ifdef::arc42help[] +[role="arc42help"] +**** +.Contents +Any requirement that constraints software architects in their freedom of design and implementation decisions or decision about the development process. These constraints sometimes go beyond individual systems and are valid for whole organizations and companies. + +.Motivation +Architects should know exactly where they are free in their design decisions and where they must adhere to constraints. +Constraints must always be dealt with; they may be negotiable, though. + +.Form +Simple tables of constraints with explanations. +If needed you can subdivide them into +technical constraints, organizational and political constraints and +conventions (e.g. programming or versioning guidelines, documentation or naming conventions) + + +.Further Information + +See https://docs.arc42.org/section-2/[Architecture Constraints] in the arc42 documentation. + +**** +endif::arc42help[] diff --git a/docs/arc42/chapters/03_context_and_scope.adoc b/docs/arc42/chapters/03_context_and_scope.adoc new file mode 100644 index 000000000..a1ba2e224 --- /dev/null +++ b/docs/arc42/chapters/03_context_and_scope.adoc @@ -0,0 +1,195 @@ +:jbake-title: Context and Scope +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 3 +:filename: /chapters/03_context_and_scope.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-context-and-scope]] +== Context and Scope + +=== System Boundaries: What's IN vs OUT + +**IN SCOPE (JBake responsibilities):** + +* Parsing markup files (Markdown, AsciiDoc, HTML, Raw HTML) +* Template rendering with multiple engine support +* Content database management (caching, querying) +* Static file copying (assets, media) +* Blog-specific features (archives, tags, feeds, sitemap) +* Local preview server +* Command-line interface +* Configuration management + +**OUT OF SCOPE (External responsibilities):** + +* Content authoring (use your favorite editor) +* Version control (use Git, SVN, etc.) +* Web hosting/deployment (use FTP, rsync, GitHub Pages, etc.) +* Dynamic server-side processing +* User authentication/sessions +* Database-backed content management +* Real-time collaboration +* WYSIWYG editing + +=== Business Context + +[plantuml, jbake-context, svg] +---- +@startuml +!include + +title JBake System Context + +Person(dev, "Developer", "Content creator and site maintainer") +Person(reader, "Site Visitor", "Reads the published static site") + +System(jbake, "JBake", "Static site generator that transforms markup content into HTML websites") + +System_Ext(editor, "Text Editor", "VS Code, IntelliJ, vim, etc.") +System_Ext(vcs, "Version Control", "Git repository for content") +System_Ext(webserver, "Web Server", "Nginx, Apache, GitHub Pages") +System_Ext(browser, "Web Browser", "For preview and consumption") + +Rel(dev, editor, "Writes content in", "Markdown/AsciiDoc") +Rel(dev, jbake, "Runs bake command", "CLI") +Rel(dev, vcs, "Commits content", "Git") +Rel(jbake, browser, "Serves preview", "HTTP :8820") +Rel(dev, webserver, "Deploys output", "FTP/rsync/Git") +Rel(reader, browser, "Accesses site") +Rel(browser, webserver, "Loads pages", "HTTPS") + +note right of jbake + Core boundary: Transform static + files IN, generate HTML OUT. + No dynamic processing, no CMS. +end note + +@enduml +---- + +**Communication Partners:** + +[options="header",cols="2,3,3"] +|=== +|Partner |Inputs to JBake |Outputs from JBake + +|**Developer/User** +|CLI commands (`-b`, `-s`, `-i`), Configuration files (`jbake.properties`), Content files, Template files +|Generated HTML site, Console logs, Error messages, Example projects + +|**File System** +|Content folder structure, Asset files (CSS/JS/images), Data files (JSON/YAML) +|Output folder with rendered HTML, Copied assets, Generated feeds/sitemap + +|**Template Engines** (Freemarker, Groovy, etc.) +|Template files (`.ftl`, `.groovy`, `.tpl`, etc.), Template model (data) +|Rendered HTML strings + +|**Markup Engines** (AsciiDoctor, Flexmark) +|Raw markup text (AsciiDoc, Markdown) +|Parsed HTML fragments, Metadata maps + +|**OrientDB** +|Document models for storage, SHA1 hashes, Query requests +|Cached documents, Query results, Change detection signals + +|**Preview Server (Jetty)** +|Serve requests from JBake +|HTTP responses with generated content +|=== + +=== Technical Context + +[plantuml, jbake-technical-context, svg] +---- +@startuml +!include + +title JBake Technical Context + +Person(user, "User", "") + +Container(jbake, "JBake CLI", "Java 8+", "Command-line application") + +ContainerDb(orient, "OrientDB", "Embedded DB", "In-memory or plocal storage") + +Container_Ext(asciidoctor, "AsciidoctorJ", "JRuby", "AsciiDoc processor") +Container_Ext(flexmark, "Flexmark", "Java", "Markdown processor") +Container_Ext(freemarker, "Freemarker", "Java", "Template engine") +Container_Ext(groovy, "Groovy Templates", "Groovy", "Template engine") +Container_Ext(thymeleaf, "Thymeleaf", "Java", "Template engine") +Container_Ext(jetty, "Jetty Server", "Java", "HTTP server") + +Rel(user, jbake, "Executes", "java -jar jbake.jar") +Rel(jbake, orient, "Stores/queries", "OrientDB API") +Rel(jbake, asciidoctor, "Processes .adoc", "Java API") +Rel(jbake, flexmark, "Processes .md", "Java API") +Rel(jbake, freemarker, "Renders .ftl", "Java API") +Rel(jbake, groovy, "Renders .groovy", "Java API") +Rel(jbake, thymeleaf, "Renders .html", "Java API") +Rel(jbake, jetty, "Starts server", "Embedded") +Rel(user, jetty, "Previews", "HTTP localhost:8820") + +@enduml +---- + +**Technical Interfaces:** + +[options="header",cols="2,2,3,3"] +|=== +|Interface |Protocol/Technology |Direction |Purpose & Rationale + +|**File I/O** +|Java NIO, Apache Commons IO +|Bidirectional +|Read source files, write output HTML. *Rationale*: Standard Java APIs for cross-platform file operations. + +|**OrientDB Embedded** +|OrientDB Java API, SQL-like queries +|In-process +|Content caching and querying. *Rationale*: Embedded NoSQL DB avoids external dependencies while providing query capabilities for tags, published posts, etc. + +|**Java SPI** (ServiceLoader) +|Java ServiceLoader mechanism +|In-process +|Plugin discovery for RenderingTool, MarkupEngine, TemplateEngine. *Rationale*: Standard Java extension mechanism for classpath-based plugins. + +|**Properties Files** +|`jbake.properties`, Commons Configuration2 +|Read +|Configuration loading. *Rationale*: Simple key-value format familiar to Java developers. + +|**META-INF Services** +|Properties files +|Read +|Engine registration (MarkupEngines.properties, TemplateEngines.properties). *Rationale*: Declarative engine-to-extension mapping. + +|**HTTP Server** +|Jetty embedded server +|TCP :8820 +|Local preview during development. *Rationale*: Lightweight embedded server, no external server required. + +|**Template Engine APIs** +|Engine-specific (Freemarker API, Groovy Template API, etc.) +|In-process +|Template processing. *Rationale*: Use native engine APIs for full feature support; wrapped behind AbstractTemplateEngine interface. + +|**Markup Parser APIs** +|AsciidoctorJ (JRuby bridge), Flexmark (native Java) +|In-process +|Content parsing. *Rationale*: Leverage mature, feature-rich parsers; AsciidoctorJ for AsciiDoc compatibility, Flexmark for modern Markdown with Pegdown compatibility. +|=== + +**Key Technical Constraints:** + +* **Java 8+ Runtime**: Minimum requirement for compatibility with older projects +* **No Native Dependencies**: Pure Java to support all platforms +* **Optional Dependencies**: Template engines and markup parsers are optional (compile-time `optional` in Gradle) to reduce footprint +* **Embedded Database**: OrientDB runs in-process; no external database server needed +* **File-Based**: All I/O through file system; no network protocols for content diff --git a/docs/arc42/chapters/04_solution_strategy.adoc b/docs/arc42/chapters/04_solution_strategy.adoc new file mode 100644 index 000000000..8824b8ba2 --- /dev/null +++ b/docs/arc42/chapters/04_solution_strategy.adoc @@ -0,0 +1,204 @@ +:jbake-title: Solution Strategy +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 4 +:filename: /chapters/04_solution_strategy.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-solution-strategy]] +== Solution Strategy + +=== Top 5 Architecture Decisions + +==== 1. Pipeline Architecture: Crawl → Parse → Render → Copy + +**Decision:** Use a linear pipeline with distinct stages: Crawler finds files, Parser extracts metadata and content, Renderer applies templates, Asset copies static files. + +**Rationale:** + +* Clear separation of concerns makes each stage independently testable +* Enables incremental builds (re-run only changed files through pipeline) +* Facilitates error handling at each stage +* Allows parallel processing potential in future + +**Consequences:** + +* ✅ Simple mental model: "input files flow through stages to output" +* ✅ Easy to debug (inspect state between stages) +* ✅ Extensible (add new stages or modify existing ones) +* ❌ Can't optimize cross-stage (e.g., skip parsing if template unchanged) +* ❌ Must pass context through all stages + +**Alternatives Considered:** + +* *Event-driven architecture*: Rejected due to complexity for simple use case +* *Monolithic "process everything" loop*: Rejected due to poor testability and maintainability + +**Related ADRs:** ADR-001-pipeline-architecture.adoc + +--- + +==== 2. Java SPI for Extensibility (Template & Markup Engines) + +**Decision:** Use Java ServiceLoader SPI pattern for discovering template engines (Freemarker, Groovy, Thymeleaf) and markup parsers (AsciiDoc, Markdown). + +**Rationale:** + +* Standard Java mechanism (no custom plugin framework) +* Classpath-based auto-discovery +* Optional dependencies (engines loaded only if on classpath) +* No runtime registration code needed + +**Consequences:** + +* ✅ Plugin developers use familiar Java patterns +* ✅ Zero-configuration plugin loading +* ✅ Reduced core size (optional engines) +* ❌ No programmatic plugin registration (must use META-INF/services) +* ❌ Classpath scanning overhead at startup + +**Alternatives Considered:** + +[options="header"] +|=== +|Criterion |SPI (Baseline) |OSGi |Custom Registry + +|Learning Curve |0 |−2 |−1 +|Startup Speed |0 |−1 |+1 +|Flexibility |0 |+2 |+1 +|Simplicity |0 |−2 |−1 +|*Total Score* |*0* |*−3* |*0* +|=== + +*Decision:* SPI chosen due to simplicity and familiarity despite tie with custom registry (simplicity weighted higher). + +**Related ADRs:** ADR-002-spi-extensibility.adoc + +--- + +==== 3. OrientDB Embedded for Content Cache + +**Decision:** Use OrientDB embedded database (in-memory or plocal) for caching parsed content and enabling queries (tags, published posts, etc.). + +**Rationale:** + +* Avoids re-parsing unchanged files (SHA1-based change detection) +* Enables SQL-like queries for blog features (tag pages, archives) +* Embedded = no external database server required +* Supports graph/document model for complex queries + +**Consequences:** + +* ✅ Fast incremental builds (only parse changed content) +* ✅ Rich query capabilities (SQL dialect) +* ✅ Zero-config embedded mode +* ❌ OrientDB dependency size (heavyweight for simple use cases) +* ❌ OrientDB v3.x migration complexity +* ❌ Schema management in code + +**Alternatives Considered:** + +[options="header"] +|=== +|Criterion |OrientDB (Baseline) |H2 Database |In-Memory Maps |SQLite + +|Query Power |0 |0 |−2 |0 +|Embedded Support |0 |0 |+2 |0 +|Dependency Size |0 |+1 |+2 |+1 +|Migration Risk |0 |0 |+2 |0 +|*Total Score* |*0* |*+1* |*+4* |*+1* +|=== + +*Hindsight:* In-memory maps would have been simpler. OrientDB chosen for query power but introduces complexity. Future versions may reconsider (see Technical Debt in ADR-003). + +**Related ADRs:** ADR-003-orientdb-cache.adoc + +--- + +==== 4. Delegating Template Engine Pattern + +**Decision:** Implement DelegatingTemplateEngine that routes to specific engines based on file extension (.ftl→Freemarker, .groovy→Groovy, etc.). + +**Rationale:** + +* Single entry point for rendering simplifies caller code +* Extension-based routing is intuitive for users +* Allows mixing template engines in single project +* Fallback mechanism if default template missing + +**Consequences:** + +* ✅ Users can choose template engine per file +* ✅ Renderer code decoupled from specific engines +* ✅ Easy to add new engines (register extension mapping) +* ❌ Must maintain extension→engine registry +* ❌ Ambiguous if multiple engines for same extension + +**Alternatives Considered:** + +* *Single engine per project*: Rejected—reduces flexibility +* *Template engine configuration*: Rejected—extension-based is more intuitive + +**Related ADRs:** ADR-004-delegating-template-engine.adoc + +--- + +==== 5. Configuration Abstraction Layer (JBakeConfiguration) + +**Decision:** Introduce JBakeConfiguration interface to abstract Apache Commons Configuration2, enabling future configuration source changes. + +**Rationale:** + +* Decouples core code from Commons Configuration API +* Enables alternative config sources (YAML, JSON, builders) +* Facilitates testing (mock configurations) +* Gradual migration from legacy CompositeConfiguration + +**Consequences:** + +* ✅ Core code independent of config implementation +* ✅ Testable without files +* ✅ Future-proof for config format changes +* ❌ Abstraction layer adds indirection +* ❌ Both old and new APIs coexist during migration (deprecation period) + +**Alternatives Considered:** + +* *Direct Commons Configuration usage*: Rejected—tight coupling +* *Builder pattern only*: Rejected—need to support file-based config + +**Related ADRs:** ADR-005-configuration-abstraction.adoc + +--- + +=== Key Quality Goals Mapping + +[options="header",cols="2,3,3"] +|=== +|Quality Goal |Solution Strategy |Implementation + +|**Extensibility** +|Java SPI for engines; Abstract base classes +|ServiceLoader discovery, AbstractTemplateEngine, MarkupEngine base classes + +|**Performance** +|Embedded cache; SHA1-based change detection +|OrientDB caching, incremental crawling, cached template compilation + +|**Simplicity** +|Convention over configuration; Sensible defaults +|`default.properties`, example projects, `-i` init command + +|**Modularity** +|Pipeline architecture; Clear interfaces +|Crawler → Parser → Renderer → Asset stages; Utensils factory pattern + +|**Compatibility** +|Deprecation strategy; Interface abstraction +|@Deprecated annotations, parallel old/new APIs, JBakeConfiguration abstraction +|=== diff --git a/docs/arc42/chapters/05_building_block_view.adoc b/docs/arc42/chapters/05_building_block_view.adoc new file mode 100644 index 000000000..34b491376 --- /dev/null +++ b/docs/arc42/chapters/05_building_block_view.adoc @@ -0,0 +1,320 @@ +:jbake-title: Building Block View +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 5 +:filename: /chapters/05_building_block_view.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-building-block-view]] + + +== Building Block View + +ifdef::arc42help[] +[role="arc42help"] +**** +.Content +The building block view shows the static decomposition of the system into building blocks (modules, components, subsystems, classes, interfaces, packages, libraries, frameworks, layers, partitions, tiers, functions, macros, operations, data structures, ...) as well as their dependencies (relationships, associations, ...) + +This view is mandatory for every architecture documentation. +In analogy to a house this is the _floor plan_. + +.Motivation +Maintain an overview of your source code by making its structure understandable through +abstraction. + +This allows you to communicate with your stakeholder on an abstract level without disclosing implementation details. + +.Form +The building block view is a hierarchical collection of black boxes and white boxes +(see figure below) and their descriptions. + +image::05_building_blocks-EN.png["Hierarchy of building blocks"] + +*Level 1* is the white box description of the overall system together with black +box descriptions of all contained building blocks. + +*Level 2* zooms into some building blocks of level 1. +Thus it contains the white box description of selected building blocks of level 1, together with black box descriptions of their internal building blocks. + +*Level 3* zooms into selected building blocks of level 2, and so on. + + +.Further Information + +See https://docs.arc42.org/section-5/[Building Block View] in the arc42 documentation. + +**** +endif::arc42help[] + +=== Whitebox Overall System + +[plantuml, jbake-level1, svg] +---- +@startuml +!include + +title JBake Level 1 - Component Landscape + +Container_Boundary(jbake, "JBake") { + Component(cli, "Launcher/CLI", "Main, Baker, LaunchOptions", "Entry point, parses commands") + Component(oven, "Oven", "Orchestrator", "Coordinates baking process") + Component(utensils, "Utensils", "Factory", "Creates and provides components") + + Component(crawler, "Crawler", "Content Discovery", "Finds files to process") + Component(parser, "Parser", "Content Parsing", "Delegates to markup engines") + Component(renderer, "Renderer", "HTML Generation", "Applies templates") + Component(asset, "Asset", "File Copier", "Copies static files") + + Component(contentstore, "ContentStore", "Cache & Queries", "OrientDB wrapper") + Component(engines, "Engine Plugins", "SPI Implementations", "Markup & Template engines") + Component(config, "JBakeConfiguration", "Settings", "Configuration access") +} + +Rel(cli, oven, "Creates & calls bake()") +Rel(oven, utensils, "Gets components") +Rel(oven, crawler, "Crawls content") +Rel(oven, renderer, "Renders documents") +Rel(oven, asset, "Copies assets") + +Rel(crawler, parser, "Parses files") +Rel(parser, engines, "Uses markup engines") +Rel(renderer, engines, "Uses template engines") + +Rel(crawler, contentstore, "Stores documents") +Rel(renderer, contentstore, "Queries documents") + +Rel_U(utensils, config, "Provides config to all") + +@enduml +---- + +**Motivation:** + +The system is decomposed into distinct layers following a *pipeline architecture* combined with a *factory pattern*: + +1. **Entry Layer (CLI/Launcher)**: Handles user interaction and command parsing +2. **Orchestration Layer (Oven)**: Coordinates the baking workflow +3. **Processing Layer (Crawler, Parser, Renderer, Asset)**: Performs core transformations +4. **Extension Layer (Engines)**: Pluggable parsers and renderers +5. **Infrastructure Layer (ContentStore, Configuration)**: Provides caching and settings + +**Key Design Principles:** + +* *Separation of Concerns*: Each component has a single responsibility +* *Dependency Injection*: Utensils factory creates configured instances +* *Plugin Architecture*: Engines loaded via ServiceLoader SPI +* *Abstraction*: Interfaces hide implementation details (JBakeConfiguration, AbstractTemplateEngine) + +**Contained Building Blocks:** + +[options="header",cols="2,3,3"] +|=== +|Component |Responsibility |Key Constraint + +|**Launcher/CLI** +(`Main`, `Baker`, `LaunchOptions`) +|Parse command-line arguments, validate options, initiate baking process +|Must remain backwards compatible for script users; uses picocli for arg parsing + +|**Oven** +(`Oven`) +|Orchestrate baking: initialize DB, run crawler, render content, copy assets, handle errors +|Central coordinator; owns the lifecycle; delegates actual work to specialists + +|**Utensils** +(`Utensils`, `UtensilsFactory`) +|Factory for creating and wiring components (Crawler, Parser, Renderer, ContentStore) +|Ensures all components use same configuration instance + +|**Crawler** +(`Crawler`) +|Recursively scan content folder, detect changed files (SHA1), pass files to Parser +|Must respect `.jbakeignore` files; handles both content and data files + +|**Parser** +(`Parser`, `ParserEngine` implementations) +|Delegate to appropriate markup engine based on file extension, extract metadata & body +|File extension determines parser; engines are optional dependencies + +|**Renderer** +(`Renderer`, `RenderingTool` implementations) +|Apply templates to content, generate HTML, handle pagination, create feeds/sitemap +|Uses ServiceLoader to find RenderingTool implementations; each tool renders specific content types + +|**Asset** +(`Asset`) +|Copy static files (CSS, JS, images) from asset folder and content folder to destination +|Preserves folder structure; supports asset ignore patterns + +|**ContentStore** +(`ContentStore`) +|Wrapper around OrientDB; cache parsed content, query by type/tag/status; change detection +|Embedded database (memory or plocal); stores DocumentModel instances; SQL-like query interface + +|**Engine Plugins** +(Markup & Template) +|Parse markup formats (Markdown, AsciiDoc); render templates (Freemarker, Groovy, Thymeleaf) +|Loaded via Java SPI; optional dependencies; must extend AbstractTemplateEngine or implement MarkupEngine + +|**JBakeConfiguration** +(`JBakeConfiguration`) +|Provide access to all configuration properties with type-safe methods +|Abstracts underlying Commons Configuration2; immutable after creation +|=== + +**Important Interfaces:** + +* `JBakeConfiguration`: Primary configuration contract +* `ParserEngine`: Contract for markup parsers (`parse(config, file) → DocumentModel`) +* `AbstractTemplateEngine`: Base for template renderers (`renderDocument(model, template, writer)`) +* `RenderingTool`: SPI for custom renderers (`render(renderer, contentStore, config) → int`) + +ifdef::arc42help[] +[role="arc42help"] +**** +Insert your explanations of black boxes from level 1: + +If you use tabular form you will only describe your black boxes with name and +responsibility according to the following schema: + +[cols="1,2" options="header"] +|=== +| **Name** | **Responsibility** +| __ | __ +| __ | __ +|=== + + + +If you use a list of black box descriptions then you fill in a separate black box template for every important building block . +Its headline is the name of the black box. +**** +endif::arc42help[] + +==== + +ifdef::arc42help[] +[role="arc42help"] +**** +Here you describe +according the the following black box template: + +* Purpose/Responsibility +* Interface(s), when they are not extracted as separate paragraphs. This interfaces may include qualities and performance characteristics. +* (Optional) Quality-/Performance characteristics of the black box, e.g.availability, run time behavior, .... +* (Optional) directory/file location +* (Optional) Fulfilled requirements (if you need traceability to requirements). +* (Optional) Open issues/problems/risks + +**** +endif::arc42help[] + +__ + +__ + +_<(Optional) Quality/Performance Characteristics>_ + +_<(Optional) Directory/File Location>_ + +_<(Optional) Fulfilled Requirements>_ + +_<(optional) Open Issues/Problems/Risks>_ + + + + +==== + +__ + +==== + +__ + + +==== + +... + +==== + + + +=== Level 2 + +ifdef::arc42help[] +[role="arc42help"] +**** +Here you can specify the inner structure of (some) building blocks from level 1 as white boxes. + +You have to decide which building blocks of your system are important enough to justify such a detailed description. +Please prefer relevance over completeness. Specify important, surprising, risky, complex or volatile building blocks. +Leave out normal, simple, boring or standardized parts of your system +**** +endif::arc42help[] + +==== White Box __ + +ifdef::arc42help[] +[role="arc42help"] +**** +...describes the internal structure of _building block 1_. +**** +endif::arc42help[] + +__ + +==== White Box __ + + +__ + +... + +==== White Box __ + + +__ + + + +=== Level 3 + +ifdef::arc42help[] +[role="arc42help"] +**** +Here you can specify the inner structure of (some) building blocks from level 2 as white boxes. + +When you need more detailed levels of your architecture please copy this +part of arc42 for additional levels. +**** +endif::arc42help[] + +==== White Box <_building block x.1_> + +ifdef::arc42help[] +[role="arc42help"] +**** +Specifies the internal structure of _building block x.1_. +**** +endif::arc42help[] + +__ + + +==== White Box <_building block x.2_> + +__ + + + +==== White Box <_building block y.1_> + +__ diff --git a/docs/arc42/chapters/06_runtime_view.adoc b/docs/arc42/chapters/06_runtime_view.adoc new file mode 100644 index 000000000..ee15b337d --- /dev/null +++ b/docs/arc42/chapters/06_runtime_view.adoc @@ -0,0 +1,63 @@ +:jbake-title: Runtime View +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 6 +:filename: /chapters/06_runtime_view.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-runtime-view]] +== Runtime View + + +ifdef::arc42help[] +[role="arc42help"] +**** +.Contents +The runtime view describes concrete behavior and interactions of the system’s building blocks in form of scenarios from the following areas: + +* important use cases or features: how do building blocks execute them? +* interactions at critical external interfaces: how do building blocks cooperate with users and neighboring systems? +* operation and administration: launch, start-up, stop +* error and exception scenarios + +Remark: The main criterion for the choice of possible scenarios (sequences, workflows) is their *architectural relevance*. It is *not* important to describe a large number of scenarios. You should rather document a representative selection. + +.Motivation +You should understand how (instances of) building blocks of your system perform their job and communicate at runtime. +You will mainly capture scenarios in your documentation to communicate your architecture to stakeholders that are less willing or able to read and understand the static models (building block view, deployment view). + +.Form +There are many notations for describing scenarios, e.g. + +* numbered list of steps (in natural language) +* activity diagrams or flow charts +* sequence diagrams +* BPMN or EPCs (event process chains) +* state machines +* ... + + +.Further Information + +See https://docs.arc42.org/section-6/[Runtime View] in the arc42 documentation. + +**** +endif::arc42help[] + +=== + + +* __ +* __ + +=== + +=== ... + +=== diff --git a/docs/arc42/chapters/07_deployment_view.adoc b/docs/arc42/chapters/07_deployment_view.adoc new file mode 100644 index 000000000..711509b90 --- /dev/null +++ b/docs/arc42/chapters/07_deployment_view.adoc @@ -0,0 +1,110 @@ +:jbake-title: Deployment View +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 7 +:filename: /chapters/07_deployment_view.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-deployment-view]] + + +== Deployment View + +ifdef::arc42help[] +[role="arc42help"] +**** +.Content +The deployment view describes: + + 1. technical infrastructure used to execute your system, with infrastructure elements like geographical locations, environments, computers, processors, channels and net topologies as well as other infrastructure elements and + +2. mapping of (software) building blocks to that infrastructure elements. + +Often systems are executed in different environments, e.g. development environment, test environment, production environment. In such cases you should document all relevant environments. + +Especially document a deployment view if your software is executed as distributed system with more than one computer, processor, server or container or when you design and construct your own hardware processors and chips. + +From a software perspective it is sufficient to capture only those elements of an infrastructure that are needed to show a deployment of your building blocks. Hardware architects can go beyond that and describe an infrastructure to any level of detail they need to capture. + +.Motivation +Software does not run without hardware. +This underlying infrastructure can and will influence a system and/or some +cross-cutting concepts. Therefore, there is a need to know the infrastructure. + +.Form + +Maybe a highest level deployment diagram is already contained in section 3.2. as +technical context with your own infrastructure as ONE black box. In this section one can +zoom into this black box using additional deployment diagrams: + +* UML offers deployment diagrams to express that view. Use it, probably with nested diagrams, +when your infrastructure is more complex. +* When your (hardware) stakeholders prefer other kinds of diagrams rather than a deployment diagram, let them use any kind that is able to show nodes and channels of the infrastructure. + + +.Further Information + +See https://docs.arc42.org/section-7/[Deployment View] in the arc42 documentation. + +**** +endif::arc42help[] + +=== Infrastructure Level 1 + +ifdef::arc42help[] +[role="arc42help"] +**** +Describe (usually in a combination of diagrams, tables, and text): + +* distribution of a system to multiple locations, environments, computers, processors, .., as well as physical connections between them +* important justifications or motivations for this deployment structure +* quality and/or performance features of this infrastructure +* mapping of software artifacts to elements of this infrastructure + +For multiple environments or alternative deployments please copy and adapt this section of arc42 for all relevant environments. +**** +endif::arc42help[] + +_****_ + +Motivation:: + +__ + +Quality and/or Performance Features:: + +__ + +Mapping of Building Blocks to Infrastructure:: +__ + + +=== Infrastructure Level 2 + +ifdef::arc42help[] +[role="arc42help"] +**** +Here you can include the internal structure of (some) infrastructure elements from level 1. + +Please copy the structure from level 1 for each selected element. +**** +endif::arc42help[] + +==== __ + +__ + +==== __ + +__ + +... + +==== __ + +__ diff --git a/docs/arc42/chapters/08_concepts.adoc b/docs/arc42/chapters/08_concepts.adoc new file mode 100644 index 000000000..0edd9e322 --- /dev/null +++ b/docs/arc42/chapters/08_concepts.adoc @@ -0,0 +1,267 @@ +:jbake-title: Cross-cutting Concepts +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 8 +:filename: /chapters/08_concepts.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-concepts]] +== Cross-cutting Concepts + +=== Mental Model Map + +==== Core Metaphor: The Bakery + +Think of JBake as a *bakery that transforms raw ingredients into finished baked goods*: + +* **Content files** (Markdown/AsciiDoc) = Raw ingredients (flour, eggs, sugar) +* **Templates** (Freemarker/Groovy/etc.) = Recipes +* **Parser** = Preparation (mixing, measuring) +* **Oven** = Baking process (transformation) +* **Output HTML** = Finished products ready to serve +* **ContentStore (OrientDB)** = Pantry/inventory (caching what's already prepared) +* **Crawler** = Ingredient sourcing (finding what needs to be baked) + +This metaphor guides the entire architecture and naming conventions. + +==== Must-Understand Concepts + +===== 1. Document Model as Universal Data Structure + +**What it is:** `DocumentModel` is a map-based structure representing parsed content with metadata. + +**Why it matters:** Every content file becomes a DocumentModel, which flows through the entire pipeline. Understanding this structure is key to: + +* Writing templates (accessing `content.title`, `content.body`, `content.tags`) +* Creating custom parsers +* Querying ContentStore +* Debugging rendering issues + +**Impact on development:** + +* Add custom metadata by prefixing keys in content headers +* Template engines receive DocumentModel as `content` variable +* Database queries return `List` + +**Common newcomer mistakes:** + +* ❌ Assuming DocumentModel is strongly typed → it's a Map, keys are strings +* ❌ Mutating DocumentModel after parsing → mutations affect cached version +* ❌ Forgetting required fields (type, status) → content won't render + +**Validation:** Can you explain how a custom field `myCustomField: value` in a Markdown header becomes accessible in templates as `${content.myCustomField}`? + +===== 2. The Dual Nature of Status & Type + +**What it is:** Every document has `status` (published/draft) and `type` (post/page/custom). + +**Why it matters:** + +* `status` controls rendering: only `published` appears in output +* `type` determines template used (`post.ftl`, `page.ftl`, `mytype.ftl`) +* Database queries filter by both (`getPublishedPosts()`) + +**Impact on development:** + +* Creating custom document types requires: + 1. Defining `template..file` in config + 2. Creating corresponding template file + 3. Registering type in DocumentTypes +* Default status/type in config affects files without headers + +**Common newcomer mistakes:** + +* ❌ Creating content without status → defaults to `draft`, won't render +* ❌ Assuming type affects URL structure → type is for templates only +* ❌ Hardcoding types in queries → use DocumentTypes.getDocumentTypes() + +**Validation:** Why would a file with `type: tutorial` and `status: published` not appear on the site? (Answer: Missing `template.tutorial.file` configuration or template file) + +===== 3. SHA1-based Change Detection + +**What it is:** JBake computes SHA1 hash of each file; reprocesses only if hash changed. + +**Why it matters:** Enables incremental builds on large sites (1000+ pages). + +**How it works:** + +1. Crawler computes SHA1 of file content +2. Queries ContentStore for existing document with same sourceUri +3. Compares SHA1 hashes +4. If identical → `IDENTICAL` status (skip) +5. If different → `UPDATED` status (delete old, reparse) +6. If not found → `NEW` status (parse) + +**Impact on development:** + +* Template changes don't trigger content re-parsing (separate signature) +* Renaming a file creates new document (different URI) +* Binary files copied based on file-level change, not SHA1 + +**Common newcomer mistakes:** + +* ❌ Expecting template changes to re-render without `-clearCache` → templates have separate signature +* ❌ Editing file, bake shows old content → check file encoding, line endings +* ❌ Wondering why assets always copy → assets aren't SHA1-checked (performance) + +**Validation:** If you change a single word in one Markdown file in a 500-file site, how many files does JBake re-parse? (Answer: 1) + +===== 4. Template Engine Discovery & Delegation + +**What it is:** `DelegatingTemplateEngine` routes to specific engines based on file extension. + +**Why it matters:** + +* Multiple template engines can coexist in one project +* Extension determines engine: `.ftl` → Freemarker, `.groovy` → Groovy +* Fallback mechanism if default template missing + +**How it works:** + +1. `TemplateEngines` registry loads engines via SPI (META-INF/services) +2. Each engine registers file extensions it handles +3. `DelegatingTemplateEngine.renderDocument()` looks up extension +4. Calls specific engine's `renderDocument()` +5. If template not found, tries other extensions + +**Impact on development:** + +* Adding template engine: implement `AbstractTemplateEngine`, add to META-INF/services, add extension to properties file +* Mixing engines: `index.ftl` (Freemarker), `post.groovy` (Groovy) in same project +* Template not found: JBake tries alternative extensions automatically + +**Common newcomer mistakes:** + +* ❌ Creating `post.html` expecting Thymeleaf → `.html` goes to raw passthrough; use `.thymeleaf` or configure extension mapping +* ❌ Engine on classpath but not loaded → missing META-INF/services entry +* ❌ Template errors don't show engine name → check engine-specific logs + +**Validation:** If you have Freemarker and Groovy on classpath, and create `index.ftl` and `index.groovy`, which one is used? (Answer: Depends on `template.masterindex.file` config; default is `.ftl`) + +===== 5. ServiceLoader SPI Extension Pattern + +**What it is:** Java's `ServiceLoader` mechanism for discovering plugins at runtime. + +**Why it matters:** Core extensibility mechanism for: + +* `RenderingTool` → Custom renderers (feed, sitemap, archives) +* `MarkupEngine` → Custom parsers (Textile, Org-mode) +* `TemplateEngine` → Custom template languages + +**How to extend:** + +1. Implement interface (e.g., `RenderingTool`) +2. Create `META-INF/services/org.jbake.render.RenderingTool` file +3. Add fully-qualified class name to file +4. Add JAR to classpath +5. JBake auto-discovers via `ServiceLoader.load(RenderingTool.class)` + +**Impact on development:** + +* No core modification needed for plugins +* Plugins deployed as separate JARs +* Load order non-deterministic (use Priority pattern if needed) + +**Common newcomer mistakes:** + +* ❌ Forgetting META-INF/services file → plugin not loaded, no error +* ❌ Typo in service file → ClassNotFoundException +* ❌ Expecting plugins to replace core components → SPI is additive, not replacement + +**Validation:** Name the 3 places where ServiceLoader is used in JBake. (Answer: RenderingTool, MarkupEngine registration, TemplateEngine registration) + +=== Cross-Cutting Concerns + +==== Error Handling Strategy + +**Philosophy:** Collect errors, don't fail fast. + +**Implementation:** + +* `Oven` aggregates errors from all stages in `List` +* Rendering errors caught per-tool, don't halt other tools +* Asset copy errors logged but don't stop bake +* Parser returns `null` on failure (logged, not thrown) +* Final check: if `errors.size() > 0` → throw `JBakeException` with summary + +**Rationale:** Large sites may have many files; one bad file shouldn't block 99 good files from rendering. + +==== Logging Conventions + +* `SLF4J` facade, delegate to user's choice (Logback in tests) +* **INFO**: Progress ("Baking has started", "Parsed 50 files") +* **DEBUG**: Detailed operations ("Create document class", SQL queries) +* **WARN**: Recoverable issues (template not found, using fallback) +* **ERROR**: Unrecoverable failures (no template engine for extension) + +==== Configuration Hierarchy + +1. **Built-in defaults**: `default.properties` resource +2. **User project**: `jbake.properties` file +3. **Custom config**: Can pass custom properties programmatically + +**Layering:** CompositeConfiguration merges in order; later sources override earlier. + +**Access Pattern:** Always use `JBakeConfiguration` interface, not direct Commons Configuration. + +==== Security Considerations + +* **No authentication**: Static site generator, no server-side auth +* **Path traversal**: Validate source folder, no `..` traversal in content paths +* **Template injection**: User controls templates; not a sandbox (trust your templates) +* **AsciidoctorJ**: `SafeMode.UNSAFE` for full feature access (user content trusted) + +==== Performance Patterns + +1. **Caching**: OrientDB stores parsed content; SHA1 avoids re-parse +2. **Lazy loading**: Template engines initialized on first use +3. **Batch operations**: OrientDB bulk inserts for documents +4. **Optional dependencies**: Engines loaded only if on classpath +5. **Incremental builds**: Single-file bake for watch mode + +=== Unwritten Rules & Team Conventions + +These conventions are followed but not always documented: + +1. **Deprecation over breaking changes**: Old APIs marked @Deprecated, not removed +2. **File over API**: Prefer file-based config/content over programmatic for user simplicity +3. **Fail gracefully**: Return null/empty rather than throw exceptions in core path +4. **Test with fixtures**: `src/test/resources/fixture/` contains canonical test data structure +5. **Example projects**: Every template engine has example ZIP for `-init` command +6. **No global mutable state**: Except DocumentTypes, ModelExtractors (historical; refactor candidate) + +=== Failed Experiments & Lessons Learned + +==== Attempt: Direct OrientDB Query Language Exposure + +**What was tried:** Expose OrientDB SQL directly to templates for custom queries. + +**Why it didn't work:** + +* OrientDB SQL dialect changed between versions (v2 → v3) +* Tight coupling to database implementation +* Template authors needed to learn OrientDB specifics + +**Current approach:** ContentStore wraps queries with Java methods (`getPublishedPosts()`, `getTaggedPosts()`). Stable API despite OrientDB changes. + +**Lesson:** Abstract external dependencies; don't leak third-party APIs into user-facing interfaces. + +==== Attempt: Multiple Configuration File Formats + +**What was tried:** Support YAML, JSON, Properties for `jbake.properties`. + +**Why abandoned:** + +* Commons Configuration2 supports multiple formats, but: +* Each format has subtle parsing differences (YAML indentation, JSON strict quotes) +* Support burden for troubleshooting configurations +* Properties file well-understood by Java community + +**Current approach:** Properties file only; use data files (YAML/JSON) for content data, not config. + +**Lesson:** Flexibility has costs; sometimes one well-supported format beats many partially-supported formats. diff --git a/docs/arc42/chapters/09_architecture_decisions.adoc b/docs/arc42/chapters/09_architecture_decisions.adoc new file mode 100644 index 000000000..a4eb745b7 --- /dev/null +++ b/docs/arc42/chapters/09_architecture_decisions.adoc @@ -0,0 +1,199 @@ +:jbake-title: Architecture Decisions +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 9 +:filename: /chapters/09_architecture_decisions.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-design-decisions]] +== Architecture Decisions + +JBake's architecture has evolved through several phases, with key decisions documented as Architecture Decision Records (ADRs). These decisions capture the context, rationale, alternatives considered, and consequences of significant architectural choices. + +For detailed ADR documentation including full context, alternatives analysis, and implementation notes, see the link:../decisions/README.adoc[Architecture Decision Records directory]. + +=== Overview of Key Decisions + +==== Phase 1: Foundation (2012-2014) - "Get it Working" + +===== link:../decisions/ADR-001-pipeline-architecture.adoc[ADR-001: Pipeline Architecture] + +**Status:** ✅ Accepted | **Date:** 2012-09-15 + +**Decision:** Adopt a linear pipeline architecture with four distinct stages (Crawl → Parse → Render → Copy). + +**Rationale:** Provides a simple, understandable mental model. Each stage has clear inputs/outputs and can be tested independently. Alternative event-driven and monolithic approaches were rejected for unnecessary complexity. + +**Consequences:** +* ✅ Easy to explain and maintain +* ✅ Good testability and debuggability +* ❌ Sequential execution limits parallelization + +===== link:../decisions/ADR-002-spi-extensibility.adoc[ADR-002: Java SPI for Extensibility] + +**Status:** ✅ Accepted | **Date:** 2012-10-20 + +**Decision:** Use Java's Service Provider Interface (SPI) mechanism for all extensibility points (markup engines, template engines, model extractors). + +**Rationale:** Zero-configuration plugin discovery. Drop a JAR in classpath and it works. Standard Java mechanism familiar to developers. + +**Consequences:** +* ✅ Clean separation between core and extensions +* ✅ Embeddability (only include what you need) +* ❌ Discovery overhead at startup +* ❌ No API version compatibility handling + +===== link:../decisions/ADR-003-orientdb-cache.adoc[ADR-003: OrientDB Embedded Database] + +**Status:** ✅ Accepted (with considerations) | **Date:** 2012-11-05 + +**Decision:** Use OrientDB as an embedded, in-memory document database for the ContentStore. + +**Rationale:** Rich SQL-like querying capabilities, schemaless document model fits content metadata, no external database server needed. + +**Consequences:** +* ✅ Fast queries with pagination support +* ✅ Flexible schema (metadata varies by document type) +* ❌ Heavy dependency (~5MB + transitive dependencies) +* ❌ Thread safety requires careful lifecycle management + +==== Phase 2: Maturity (2015-2017) - "Make it Extensible" + +===== link:../decisions/ADR-004-delegating-template-engine.adoc[ADR-004: Delegating Template Engine Pattern] + +**Status:** ✅ Accepted | **Date:** 2013-04-10 + +**Decision:** Implement Delegating Template Engine pattern with SPI-based engine discovery to support multiple template engines (Freemarker, Groovy, Thymeleaf, Jade, Pebble). + +**Rationale:** Allows users to choose their preferred template engine. File extension determines which engine to use. Core rendering pipeline decoupled from specific implementations. + +**Consequences:** +* ✅ Easy to add new template engines +* ✅ Users can mix engines in one project +* ❌ Extra indirection layer +* ❌ Deeper stack traces for errors + +===== link:../decisions/ADR-005-configuration-abstraction.adoc[ADR-005: Configuration Abstraction Layer] + +**Status:** ✅ Accepted | **Date:** 2016-03-15 + +**Decision:** Introduce `JBakeConfiguration` interface with `DefaultJBakeConfiguration` implementation, hiding Apache Commons Configuration behind abstraction. + +**Rationale:** Decouples core from configuration library, enables type safety, improves testability with mockable interface, allows future evolution. + +**Consequences:** +* ✅ Type-safe accessors (e.g., `getDestinationFolder()` returns `File`) +* ✅ Easy to mock for testing +* ❌ Verbosity (50+ getter methods) +* ❌ Maintenance overhead (adding property requires multiple updates) + +===== link:../decisions/ADR-006-incremental-builds.adoc[ADR-006: SHA1-based Incremental Builds] + +**Status:** ✅ Accepted | **Date:** 2013-08-20 + +**Decision:** Implement SHA1-based incremental builds at the Crawler stage with template and configuration change detection. + +**Rationale:** Massive performance improvement for large sites. SHA1 hashing detects actual content changes (more reliable than timestamps). Most builds process < 5% changed files. + +**Consequences:** +* ✅ 30s → 2s for single-file changes +* ✅ Fast developer feedback during writing +* ❌ First build still slow (no cache) +* ❌ Cross-dependencies (tags, archives) always regenerate + +==== Phase 3: Modernization (2018-Present) - "Reduce Tech Debt" + +===== link:../decisions/ADR-007-deprecation-strategy.adoc[ADR-007: Backwards Compatibility via Deprecation] + +**Status:** ✅ Accepted | **Date:** 2017-06-10 + +**Decision:** Adopt deprecation-first strategy with semantic versioning and multi-version deprecation cycles (2 major versions minimum). + +**Rationale:** Balances innovation with stability. Users need time to migrate. Ecosystem (themes, plugins) requires predictability. + +**Consequences:** +* ✅ User confidence (projects continue working) +* ✅ Clear expectations via semantic versioning +* ❌ Maintenance burden (support old + new APIs) +* ❌ Slower evolution (can't remove bad decisions quickly) + +===== link:../decisions/ADR-008-java-version-policy.adoc[ADR-008: Java Version Support Policy] + +**Status:** ✅ Accepted | **Date:** 2019-09-20 + +**Decision:** Adopt "Current LTS + Previous LTS" policy. Minimum Java 8 (as of JBake 2.6.0+), plan to require Java 11 in JBake 3.0. + +**Rationale:** Access modern language features (lambdas, streams, Optional), library compatibility, 70%+ users already on Java 8+. Java 7 EOL since 2015. + +**Consequences:** +* ✅ Modern code with Java 8+ features +* ✅ Access to modern dependencies +* ❌ 20% on Java 7 must upgrade or stay on old JBake +* ❌ Enterprise friction (some organizations slow to upgrade) + +=== Decision Dependencies + +The ADRs are interconnected, building on each other: + +* **ADR-002 (SPI)** enables **ADR-004 (Delegating Template Engine)** through plugin discovery +* **ADR-003 (OrientDB)** supports **ADR-006 (Incremental Builds)** via SHA1 storage +* **ADR-001 (Pipeline)** provides structure where **ADR-006 (Incremental Builds)** optimizes Crawler stage +* **ADR-005 (Configuration Abstraction)** demonstrates **ADR-007 (Deprecation Strategy)** through gradual API migration +* **ADR-008 (Java Version)** balances compatibility concerns from **ADR-007 (Deprecation Strategy)** + +=== Current Architecture State (2025) + +The combination of these decisions has produced: + +* **Mature extensibility**: SPI-based plugins for markup and templates +* **Good performance**: Incremental builds for large sites +* **Stable API**: Clear deprecation policy maintains ecosystem +* **Modern foundation**: Java 8+ enables clean, maintainable code +* **Known trade-offs**: OrientDB dependency, sequential pipeline, limited parallelization + +=== Future Considerations + +Areas under consideration for future ADRs: + +* **Persistent caching**: Cross-build ContentStore persistence +* **Parallel processing**: Multi-threaded rendering +* **Storage alternatives**: Lighter alternatives to OrientDB +* **Dependency graph**: Smart rebuild based on content relationships +* **Module system**: Java 9+ module adoption + +ifdef::arc42help[] +[role="arc42help"] +**** +.Contents +Important, expensive, large scale or risky architecture decisions including rationales. +With "decisions" we mean selecting one alternative based on given criteria. + +Please use your judgement to decide whether an architectural decision should be documented +here in this central section or whether you better document it locally +(e.g. within the white box template of one building block). + +Avoid redundancy. +Refer to section 4, where you already captured the most important decisions of your architecture. + +.Motivation +Stakeholders of your system should be able to comprehend and retrace your decisions. + +.Form +Various options: + +* ADR (https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions[Documenting Architecture Decisions]) for every important decision +* List or table, ordered by importance and consequences or: +* more detailed in form of separate sections per decision + +.Further Information + +See https://docs.arc42.org/section-9/[Architecture Decisions] in the arc42 documentation. +There you will find links and examples about ADR. + +**** +endif::arc42help[] diff --git a/docs/arc42/chapters/10_quality_requirements.adoc b/docs/arc42/chapters/10_quality_requirements.adoc new file mode 100644 index 000000000..8a5a6e4b0 --- /dev/null +++ b/docs/arc42/chapters/10_quality_requirements.adoc @@ -0,0 +1,122 @@ +:jbake-title: Quality Requirements +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 10 +:filename: /chapters/10_quality_requirements.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-quality-scenarios]] +== Quality Requirements + + +ifdef::arc42help[] +[role="arc42help"] +**** + +.Content +This section contains all relevant quality requirements. + +The most important of these requirements have already been described in section 1.2. (quality goals), therefore they should only be referenced here. +In this section 10 you should also capture quality requirements with lesser importance, which will not create high risks when they are not fully achieved (but might be _nice-to-have_). + +.Motivation +Since quality requirements will have a lot of influence on architectural decisions you should know what qualities are really important for your stakeholders, in a specific and measurable way. + + +.Further Information + +* See https://docs.arc42.org/section-10/[Quality Requirements] in the arc42 documentation. +* See the extensive https://quality.arc42.org[Q42 quality model on https://quality.arc42.org]. + +**** +endif::arc42help[] + + +=== Quality Requirements Overview + +ifdef::arc42help[] +[role="arc42help"] +**** + +.Content +An overview or summary of quality requirements. + + +.Motivation +Often we encounter dozens (or even hundreds) of detailed quality requirements. +In this overview section you should try to summarize, e.g. by describing categories or topics (as suggested by https://www.iso.org/obp/ui/#iso:std:iso-iec:25010:ed-2:v1:en[ISO 25010:2023] or https://quality.arc42.org[Q42]) + +If these summary descriptions are already precise, specific enough and measurable, you may skip section 10.2. + +.Form +Use a simple table in which each line contains a category or topic and a short description of the quality requirement. +Alternatively, you may use a mindmap to structure these quality requirements. +In literature, the idea of a _quality attribute tree_ has also been described, which puts the generic term "quality" as the root and uses a tree-like refinement of the term "quality". +[Bass+21] introduced the term "Quality Attribute Utility Tree" for this purpose. + + + +**** +endif::arc42help[] + + +=== Quality Scenarios + +ifdef::arc42help[] +[role="arc42help"] +**** + +.Content +Quality scenarios make quality requirements concrete and allow to decide whether they are fulfilled (in the sense of acceptance criteria). +Ensure that your scenarios are specific and measurable. + + + +Two kinds of scenarios are especially useful: + +* _Usage scenarios_ (also called application scenarios or use case scenarios) describe the system’s runtime reaction to a certain stimulus. +This also includes scenarios that describe the system’s efficiency or performance. +Example: The system reacts to a user’s request within one second. +* _Change scenarios_ describe the desired effect of a modification or extension of the system or of its immediate environment. +Example: Additional functionality is implemented or requirements for a quality attribute change, and the effort or duration of the change is measured. + + +.Form + +Typical information for detailed scenarios include the following: + +In short form (favoured in the Q42 model): + +* **Context/Background**: What kind of system or component, what is the envirionment or situation? +* **Source/Stimulus**: Who or what initiates or triggers a behaviour, reaction or action. +* **Metric/Acceptance Criteria**: A response including a _measure_ or _metric_ + + +The long form of scenarios (favoured by the SEI and [Bass+21]) is more detailed and includes the following information: + +* **Scenario ID**: A unique identifier for the scenario. +* **Scenario Name**: A short, descriptive name for the scenario. +* **Source**: The entity (user, system, or event) that initiates the scenario. +* **Stimulus**: The triggering event or condition the system must address. +* **Environment**: The operational context or condition under which the system experiences the stimulus. +* **Artifact**: The building-blocks or other elements of the system affected by the stimulus. +* **Response**: The outcome or behavior the system exhibits in reaction to the stimulus. +* **Response Measure**: The criteria or metric by which the system’s response is evaluated. + + +.Examples +See https://quality.arc42.org[the Q42 quality model website] for detailes examples of quality requirements. + +.Further Information + +* Len Bass, Paul Clements, Rick Kazman: "Software Architecture in Practice", 4th Edition, Addison-Wesley, 2021. + +**** + + +endif::arc42help[] diff --git a/docs/arc42/chapters/11_technical_risks.adoc b/docs/arc42/chapters/11_technical_risks.adoc new file mode 100644 index 000000000..d23038b52 --- /dev/null +++ b/docs/arc42/chapters/11_technical_risks.adoc @@ -0,0 +1,269 @@ +:jbake-title: Risks and Technical Debts +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 11 +:filename: /chapters/11_technical_risks.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-technical-risks]] +== Risks and Technical Debts + +=== High Priority Risks + +==== RISK-001: OrientDB Platform Dependency (Apple Silicon Incompatibility) + +**Status:** 🔴 Critical | **Probability:** High | **Impact:** High + +**Description:** + +OrientDB 3.x uses native libraries (via JNA - Java Native Access) for performance optimization, particularly for memory management and low-level database operations. These native components are platform-specific and compiled for different architectures: + +* x86_64 (Intel/AMD) - ✅ Well supported +* ARM64/aarch64 (Apple Silicon M1/M2/M3) - ❌ **Limited/No Support** + +**Manifestation:** + +On Apple Silicon Macs (M1, M2, M3 chips), JBake may: + +* Fail to start with `UnsatisfiedLinkError` when loading native libraries +* Require Rosetta 2 translation layer (performance penalty) +* Experience crashes during database operations +* Show JNA-related warnings: `JNA library 'jnidispatch' not found` + +**Impact:** + +* **Developer Experience:** New contributors on Apple Silicon cannot run JBake without workarounds +* **Build Environments:** CI/CD on ARM-based systems (e.g., GitHub Actions ARM runners) may fail +* **Production:** Cloud environments moving to ARM instances (AWS Graviton, Azure Ampere) affected +* **Adoption:** Barrier for modern Mac users (Apple Silicon adoption >80% of new Macs since 2021) + +**Current Situation (2025):** + +* OrientDB version: `3.1.20` (see `gradle.properties`) +* OrientDB 3.2.x series has improved ARM64 support, but issues remain +* OrientDB development has slowed (company pivot, reduced maintenance) +* Native library bundled in `orientdb-core` may not include ARM64 binaries + +**Workarounds (Temporary):** + +1. **Rosetta 2 Emulation:** Run JBake under x86_64 emulation ++ +[source,bash] +---- +arch -x86_64 java -jar jbake.jar +---- ++ +_Drawback: ~30-40% performance penalty_ + +2. **JVM Architecture Flag:** Force x86_64 JVM ++ +[source,bash] +---- +export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-11-x64.jdk/Contents/Home +---- ++ +_Drawback: Requires separate x86 JDK installation_ + +3. **Docker with x86 Platform:** ++ +[source,bash] +---- +docker run --platform linux/amd64 jbake/jbake +---- ++ +_Drawback: Docker overhead, emulation layer_ + +**Recommended Solutions:** + +===== Short-term (Mitigation): + +* ✅ Document the issue prominently in README and installation docs +* ✅ Provide tested workaround instructions for Apple Silicon +* ✅ Add runtime detection and warning message for ARM64 platforms +* ✅ Test with latest OrientDB 3.2.x versions (improved ARM support) +* ✅ Configure JNA system property: `-Djna.nosys=true` (already in build.gradle) + +===== Long-term (Resolution): + +* 🎯 **Replace OrientDB** with platform-independent alternative (see link:../decisions/ADR-003-orientdb-cache.adoc[ADR-003] alternatives) + - **Option 1:** Pure Java collections (Java 8+ Streams API) + - **Option 2:** H2 Database (pure Java, embedded) + - **Option 3:** Apache Derby (pure Java, embedded) + - **Option 4:** SQLite via JDBC (better platform support than OrientDB) + +* 🎯 **Incremental Migration:** Introduce abstraction layer ++ +[source,java] +---- +interface ContentRepository { + void store(DocumentModel doc); + List query(String type, Predicate filter); +} + +// Implementations: +// - OrientDBRepository (current, deprecated) +// - JavaCollectionsRepository (new default) +// - H2Repository (future) +---- + +**Tracking:** + +* Related: link:../decisions/ADR-003-orientdb-cache.adoc[ADR-003: OrientDB Embedded Database] +* GitHub Issues: + - https://github.com/jbake-org/jbake/issues/218 (OrientDB replacement discussion) + - https://github.com/orientechnologies/orientdb/issues (upstream ARM64 issues) + +**Decision Required:** + +Project maintainers should decide on migration timeline: + +* JBake 2.8.0: Add deprecation warning for OrientDB +* JBake 3.0.0: Switch to pure Java implementation (breaking change) + +--- + +=== Medium Priority Risks + +==== RISK-002: OrientDB Maintenance and Security + +**Status:** 🟡 Medium | **Probability:** Medium | **Impact:** High + +**Description:** + +OrientDB development has slowed significantly since OrientDB Ltd's business pivot in 2020-2021. The open-source project receives limited maintenance: + +* Infrequent security updates +* Slow response to critical bugs +* Uncertain long-term viability + +**Impact:** + +* **Security:** Unpatched vulnerabilities may emerge +* **Compatibility:** May not support future Java versions (Java 17+, 21) +* **Technical Debt:** Dependency on unmaintained library + +**Mitigation:** + +* Monitor OrientDB security advisories +* Consider migration timeline (see RISK-001) +* Test JBake with newer Java versions regularly + +--- + +==== RISK-003: Heavy Dependency Footprint + +**Status:** 🟡 Medium | **Probability:** High | **Impact:** Medium + +**Description:** + +OrientDB adds significant dependency weight: + +* OrientDB core: ~5MB +* Transitive dependencies: ~15MB total +* Includes unused features (graph database, distributed mode) + +**Impact:** + +* **Startup Time:** Slower initialization +* **Memory:** Higher baseline memory usage (~100MB overhead) +* **Embedded Use:** Maven/Gradle plugins pull large dependency tree +* **Distribution Size:** Larger JBake standalone package + +**Mitigation:** + +* Use dependency exclusions where possible +* Consider migration to lighter alternative (pure Java collections ~0 dependencies) + +--- + +=== Low Priority Technical Debts + +==== DEBT-001: Thread Safety Complexity + +**Status:** 🟢 Low | **Impact:** Low + +**Description:** + +OrientDB instances are thread-local, requiring `activateOnCurrentThread()` calls. This complicates multi-threaded rendering and increases risk of threading bugs. + +**Mitigation:** + +* Well-documented in code +* Consider migration to thread-safe alternative + +--- + +==== DEBT-002: Test Coverage for ContentStore + +**Status:** 🟢 Low | **Impact:** Low + +**Description:** + +ContentStore tests require OrientDB, making them slower and harder to mock. + +**Mitigation:** + +* Introduce repository abstraction (see RISK-001) +* Write tests against interface, not OrientDB directly + +--- + +=== Risk Matrix Summary + +[cols="3,1,1,1", options="header"] +|=== +|Risk/Debt |Probability |Impact |Priority + +|RISK-001: Apple Silicon Incompatibility +|High +|High +|🔴 Critical + +|RISK-002: OrientDB Maintenance +|Medium +|High +|🟡 Medium + +|RISK-003: Heavy Dependencies +|High +|Medium +|🟡 Medium + +|DEBT-001: Thread Safety +|Low +|Low +|🟢 Low + +|DEBT-002: Test Coverage +|Low +|Low +|🟢 Low +|=== + + +ifdef::arc42help[] +[role="arc42help"] +**** +.Contents +A list of identified technical risks or technical debts, ordered by priority + +.Motivation +“Risk management is project management for grown-ups” (Tim Lister, Atlantic Systems Guild.) + +This should be your motto for systematic detection and evaluation of risks and technical debts in the architecture, which will be needed by management stakeholders (e.g. project managers, product owners) as part of the overall risk analysis and measurement planning. + +.Form +List of risks and/or technical debts, probably including suggested measures to minimize, mitigate or avoid risks or reduce technical debts. + + +.Further Information + +See https://docs.arc42.org/section-11/[Risks and Technical Debt] in the arc42 documentation. + +**** +endif::arc42help[] diff --git a/docs/arc42/chapters/12_glossary.adoc b/docs/arc42/chapters/12_glossary.adoc new file mode 100644 index 000000000..bdd5e53c1 --- /dev/null +++ b/docs/arc42/chapters/12_glossary.adoc @@ -0,0 +1,54 @@ +:jbake-title: Glossary +:jbake-type: page_toc +:jbake-status: published +:jbake-menu: arc42 +:jbake-order: 12 +:filename: /chapters/12_glossary.adoc +ifndef::imagesdir[:imagesdir: ../../images] + +:toc: + + + +[[section-glossary]] +== Glossary + +ifdef::arc42help[] +[role="arc42help"] +**** +.Contents +The most important domain and technical terms that your stakeholders use when discussing the system. + +You can also see the glossary as source for translations if you work in multi-language teams. + +.Motivation +You should clearly define your terms, so that all stakeholders + +* have an identical understanding of these terms +* do not use synonyms and homonyms + + +.Form + +A table with columns and . + +Potentially more columns in case you need translations. + + +.Further Information + +See https://docs.arc42.org/section-12/[Glossary] in the arc42 documentation. + +**** +endif::arc42help[] + +[cols="e,2e" options="header"] +|=== +|Term |Definition + +| +| + +| +| +|=== diff --git a/docs/arc42/chapters/about-arc42.adoc b/docs/arc42/chapters/about-arc42.adoc new file mode 100644 index 000000000..a9d3ae472 --- /dev/null +++ b/docs/arc42/chapters/about-arc42.adoc @@ -0,0 +1,15 @@ +:homepage: https://arc42.org + +:keywords: software-architecture, documentation, template, arc42 + +:numbered!: +**About arc42** + +[role="lead"] +arc42, the template for documentation of software and system architecture. + +Template Version {revnumber}. {revremark}, {revdate} + +Created, maintained and (C) by Dr. Peter Hruschka, Dr. Gernot Starke and contributors. +See https://arc42.org. + diff --git a/docs/arc42/chapters/config.adoc b/docs/arc42/chapters/config.adoc new file mode 100644 index 000000000..4c225feda --- /dev/null +++ b/docs/arc42/chapters/config.adoc @@ -0,0 +1,10 @@ +// asciidoc settings for EN (English) +// ================================== +:toc-title: table of contents + +// enable table-of-contents +:toc: + +// where are images located? +:imagesdir: ../images +:arc42help: diff --git a/docs/arc42/decisions/ADR-001-pipeline-architecture.adoc b/docs/arc42/decisions/ADR-001-pipeline-architecture.adoc new file mode 100644 index 000000000..6de818a27 --- /dev/null +++ b/docs/arc42/decisions/ADR-001-pipeline-architecture.adoc @@ -0,0 +1,179 @@ += ADR-001: Pipeline Architecture + +Status: ✅ **Accepted** + +Date: 2012-09-15 (Estimated - early design) + +== Problem Statement + +How should JBake transform content files into a static website? What's the high-level processing model that developers can understand and maintain? + +== Context + +Building a static site generator requires: + +* Reading and parsing multiple file formats +* Applying templates to generate HTML +* Copying static assets +* Handling errors gracefully +* Supporting incremental builds + +The architecture needs to be: + +* **Understandable**: New contributors grasp the flow quickly +* **Testable**: Each stage can be tested independently +* **Maintainable**: Changes to one stage don't break others +* **Extensible**: New file formats or output types can be added + +Existing static site generators (Jekyll, Hyde) use various approaches, some monolithic, some event-driven. + +== Decision + +**Adopt a linear pipeline architecture with four distinct stages:** + +1. **Crawl**: Scan file system, identify content files +2. **Parse**: Convert markup to HTML, extract metadata +3. **Render**: Apply templates to generate final HTML +4. **Copy**: Transfer static assets to output + +**Key architectural elements:** + +* `Oven` class orchestrates the pipeline +* Each stage has clear inputs/outputs +* Stages execute sequentially +* `ContentStore` (OrientDB) sits between stages for caching +* Errors collected, not thrown, until end + +**Processing flow:** + +``` +┌─────────┐ +│ Crawler │──────> Finds files +└────┬────┘ + │ File list + ▼ +┌─────────┐ +│ Parser │──────> Extracts metadata + HTML +└────┬────┘ + │ DocumentModel + ▼ +┌──────────────┐ +│ ContentStore │──────> Caches parsed content +└────┬─────────┘ + │ Query API + ▼ +┌──────────┐ +│ Renderer │──────> Applies templates +└────┬─────┘ + │ HTML files + ▼ +┌─────────┐ +│ Asset │──────> Copies CSS/JS/images +└─────────┘ +``` + +== Consequences + +=== Positive + +* ✅ **Simple mental model**: Easy to explain ("files flow through stages") +* ✅ **Testable**: Each component tested in isolation with mock inputs +* ✅ **Debuggable**: Inspect state between stages, log each transition +* ✅ **Maintainable**: Changes localized to specific stage +* ✅ **Incremental builds**: Crawler can skip unchanged files, other stages see smaller input + +=== Negative + +* ❌ **No cross-stage optimization**: Can't skip parsing if only template changed (solved later with separate template signature) +* ❌ **Sequential execution**: Can't easily parallelize (future enhancement possible) +* ❌ **Context passing**: Must thread configuration through all stages +* ❌ **State management**: ContentStore as shared state requires careful lifecycle management + +=== Neutral + +* ℹ️ Performance is "good enough" for typical sites (< 10,000 pages) +* ℹ️ Pipeline could be replaced with different architecture without changing public API + +== Alternatives Considered + +=== Alternative 1: Event-Driven Architecture + +**Description**: Pub/sub pattern where components emit events (FileDiscovered, ContentParsed, TemplateRendered). + +**Pugh Matrix Evaluation:** + +| Criterion | Pipeline (Baseline) | Event-Driven | +|--------------------|---------------------|--------------| +| Simplicity | 0 | -2 | +| Testability | 0 | -1 | +| Extensibility | 0 | +2 | +| Debugging | 0 | -2 | +| Parallelization | 0 | +2 | +| **Total Score** | **0** | **-1** | + +**Rejection rationale**: Too complex for the problem. Event-driven excels for async, distributed systems. JBake is single-process, sequential by nature. Debugging becomes difficult (event trace instead of stack trace). Adds framework dependency (event bus). + +=== Alternative 2: Monolithic Loop + +**Description**: Single "process everything" loop that crawls, parses, renders, and copies each file in one iteration. + +**Pugh Matrix Evaluation:** + +| Criterion | Pipeline (Baseline) | Monolithic Loop | +|--------------------|---------------------|-----------------| +| Simplicity | 0 | +1 | +| Testability | 0 | -2 | +| Maintainability | 0 | -2 | +| Incremental Builds | 0 | -2 | +| **Total Score** | **0** | **-5** | + +**Rejection rationale**: Tight coupling makes testing difficult. Changes affect entire loop. Hard to implement incremental builds (can't cache parsed content separately from rendering). All logic in one class violates Single Responsibility Principle. + +=== Alternative 3: Plugin Architecture with Core Loop + +**Description**: Minimal core with plugins for each file type. Core iterates files, delegates to plugins. + +**Pugh Matrix Evaluation:** + +| Criterion | Pipeline (Baseline) | Plugin Architecture | +|--------------------|---------------------|---------------------| +| Extensibility | 0 | +2 | +| Core Simplicity | 0 | +1 | +| Consistency | 0 | -1 | +| Built-in Features | 0 | -1 | +| **Total Score** | **0** | **+1** | + +**Rejection rationale**: Close call. Plugin arch offers more flexibility but: + +* Harder to provide consistent blog features (tags, archives) - each plugin would need to implement +* Fragmentation risk: plugins may handle metadata differently +* Core-only approach keeps essential features reliable +* Pipeline *with* SPI extensions (ADR-002) provides extensibility without fragmentation + +== Implementation Notes + +**Key classes:** + +* `Oven`: Main orchestrator, owns pipeline execution +* `Crawler`: Recursive file scanner with ignore patterns +* `Parser`: Delegates to MarkupEngines based on extension +* `Renderer`: Uses TemplateEngines to generate HTML +* `Asset`: Copies files with path preservation + +**Critical design decision**: `ContentStore` as shared cache between stages. This enables: + +* Crawler stores parsed documents +* Renderer queries documents for tag pages, archives +* Incremental builds (check SHA1 before re-parsing) + +== Related Decisions + +* link:ADR-002-spi-extensibility.adoc[ADR-002]: Java SPI for Extensibility - Pluggability within pipeline +* link:ADR-003-orientdb-cache.adoc[ADR-003]: OrientDB for Caching - ContentStore implementation +* link:ADR-006-incremental-builds.adoc[ADR-006]: Incremental Build Strategy - Optimization within Crawler stage + +== References + +* Original Oven.java implementation: `jbake-core/src/main/java/org/jbake/app/Oven.java` +* Discussion: "Keep it simple" philosophy from initial commits +* Inspiration: Make (Unix tool) - ordered stages with file dependencies diff --git a/docs/arc42/decisions/ADR-002-spi-extensibility.adoc b/docs/arc42/decisions/ADR-002-spi-extensibility.adoc new file mode 100644 index 000000000..08b54b87e --- /dev/null +++ b/docs/arc42/decisions/ADR-002-spi-extensibility.adoc @@ -0,0 +1,205 @@ += ADR-002: Java SPI for Extensibility + +Status: ✅ **Accepted** + +Date: 2012-10-20 (Estimated - early extensibility phase) + +== Problem Statement + +How should JBake allow users to add support for new markup languages, template engines, and other pluggable components without modifying core code? + +== Context + +JBake needs to support multiple: + +* **Markup engines**: Markdown, AsciiDoc, HTML, etc. +* **Template engines**: Freemarker, Groovy, Thymeleaf, Jade, Pebble, etc. +* **Model extractors**: Custom data extraction for templates + +Requirements for extensibility: + +* **No core modification**: Users add new engines via dependencies only +* **Classpath discovery**: Automatic detection of available engines +* **Clear contracts**: Well-defined interfaces for extensions +* **Optional dependencies**: Core doesn't depend on all possible engines +* **Convention over configuration**: Minimal setup required + +The Java ecosystem provides several plugin mechanisms: + +* Java SPI (Service Provider Interface) - built into JDK +* OSGi - complex, heavyweight +* Custom plugin frameworks - reinventing the wheel +* Dependency injection frameworks - adds complexity + +== Decision + +**Use Java's Service Provider Interface (SPI) mechanism for all extensibility points.** + +**Key extension points:** + +1. **MarkupEngines** (`org.jbake.parser.MarkupEngines`) + - Discovers parsers via `META-INF/services/org.jbake.parser.MarkupEngines.properties` + - Properties format: `ClassName=ext1,ext2,ext3` + - Example: `org.jbake.parser.MarkdownEngine=md,markdown` + +2. **TemplateEngines** (`org.jbake.template.TemplateEngines`) + - Discovers renderers via `META-INF/services/org.jbake.template.TemplateEngines.properties` + - Same properties format + +3. **ModelExtractors** (`org.jbake.template.ModelExtractors`) + - Discovers data extractors via `META-INF/services/org.jbake.template.ModelExtractors.properties` + +**Implementation pattern:** + +```java +// 1. Core defines interface +public abstract class MarkupEngine { ... } + +// 2. Engine implementation +public class MarkdownEngine extends MarkupEngine { ... } + +// 3. Provider file: META-INF/services/org.jbake.parser.MarkupEngines.properties +org.jbake.parser.MarkdownEngine=md,markdown + +// 4. Core discovers at runtime +Enumeration resources = cl.getResources(PROPERTIES); +// ... load and register engines +``` + +**Engine loading strategy:** + +* Engines loaded lazily (on first use) +* Missing optional engines silently skipped (enables embedding) +* Explicit error if required engine not on classpath + +== Consequences + +=== Positive + +* ✅ **Zero configuration**: Drop JAR in classpath, it works +* ✅ **Standard mechanism**: Java developers familiar with SPI +* ✅ **Clean separation**: Core never imports optional engine classes directly +* ✅ **Testability**: Mock engines easily by providing test implementations +* ✅ **Embeddability**: Minimal dependencies, only include what you need +* ✅ **Multiple implementations**: Different markdown parsers can coexist + +=== Negative + +* ❌ **Discovery overhead**: Classpath scanning at startup (mitigated by caching) +* ❌ **No versioning**: SPI doesn't handle API version compatibility +* ❌ **Silent failures**: Misconfigured provider file fails at runtime, not compile time +* ❌ **Properties format**: Not type-safe, errors only caught at runtime + +=== Neutral + +* ℹ️ Properties format chosen over pure Java SPI `ServiceLoader` for flexibility (map extensions to classes) +* ℹ️ Could migrate to Java 9 module system in future without API changes + +== Alternatives Considered + +=== Alternative 1: OSGi Plugin System + +**Description**: Use OSGi bundles for hot-swappable plugins with versioning and dependency management. + +**Pugh Matrix Evaluation:** + +| Criterion | SPI (Baseline) | OSGi | +|--------------------|----------------|------| +| Simplicity | 0 | -3 | +| Setup Overhead | 0 | -3 | +| Runtime Complexity | 0 | -2 | +| Versioning | 0 | +2 | +| Hot Reload | 0 | +2 | +| **Total Score** | **0** | **-4** | + +**Rejection rationale**: Massive overkill. OSGi designed for long-running servers with hot deployment. JBake runs batch-style, exits after generation. OSGi's complexity (ClassLoaders, bundle lifecycle) not justified. Users don't need hot reload. + +=== Alternative 2: Dependency Injection Framework (Spring/Guice) + +**Description**: Use DI framework to wire components, discover plugins via component scanning. + +**Pugh Matrix Evaluation:** + +| Criterion | SPI (Baseline) | DI Framework | +|--------------------|----------------|--------------| +| Core Dependency | 0 | -2 | +| Configuration | 0 | -1 | +| Learning Curve | 0 | -2 | +| Testability | 0 | +1 | +| **Total Score** | **0** | **-4** | + +**Rejection rationale**: Adds heavyweight dependency to core. Spring/Guice designed for complex enterprise apps. JBake's needs are simple: "find classes on classpath, instantiate them". SPI does this without extra dependencies. + +=== Alternative 3: Custom Plugin Framework + +**Description**: Build custom plugin discovery with XML/JSON config files. + +**Pugh Matrix Evaluation:** + +| Criterion | SPI (Baseline) | Custom Framework | +|--------------------|----------------|------------------| +| Maintenance | 0 | -3 | +| Flexibility | 0 | +1 | +| Familiarity | 0 | -2 | +| Bugs/Edge Cases | 0 | -2 | +| **Total Score** | **0** | **-6** | + +**Rejection rationale**: Reinventing the wheel. SPI already solves this problem, tested in JDK for years. Custom framework would need documentation, edge case handling, bug fixes. Not worth it. + +== Implementation Notes + +**Engines class pattern** (example from `MarkupEngines`): + +```java +public class Engines { + private static final Engines INSTANCE; + private final Map parsers; + + static { + INSTANCE = new Engines(); + loadEngines(); + } + + private static void loadEngines() { + ClassLoader cl = Engines.class.getClassLoader(); + Enumeration resources = cl.getResources(PROPERTIES); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + Properties props = new Properties(); + props.load(url.openStream()); + for (Map.Entry entry : props.entrySet()) { + String className = (String) entry.getKey(); + String[] extensions = ((String) entry.getValue()).split(","); + registerEngine(className, extensions); + } + } + } +} +``` + +**Provider file example** (`META-INF/services/org.jbake.parser.MarkupEngines.properties`): + +```properties +org.jbake.parser.MarkdownEngine=md,markdown +org.jbake.parser.AsciidoctorEngine=ad,adoc,asciidoc +org.jbake.parser.HtmlEngine=html,htm +``` + +**Key design decisions:** + +* Singleton pattern for Engines registry (one instance per JVM) +* Static initialization ensures engines loaded before first use +* Graceful degradation: missing engines logged as warnings, not errors +* Extensions case-insensitive for user convenience + +== Related Decisions + +* link:ADR-001-pipeline-architecture.adoc[ADR-001]: Pipeline Architecture - SPI plugins integrate into pipeline stages +* link:ADR-004-delegating-template-engine.adoc[ADR-004]: Delegating Template Engine - Uses SPI to discover template engines + +== References + +* Java SPI Documentation: https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html +* MarkupEngines implementation: `jbake-core/src/main/java/org/jbake/parser/Engines.java` +* TemplateEngines implementation: `jbake-core/src/main/java/org/jbake/template/TemplateEngines.java` +* ServiceLoader test: `jbake-core/src/test/java/org/jbake/render/ServiceLoaderTest.java` diff --git a/docs/arc42/decisions/ADR-003-orientdb-cache.adoc b/docs/arc42/decisions/ADR-003-orientdb-cache.adoc new file mode 100644 index 000000000..29e954b1a --- /dev/null +++ b/docs/arc42/decisions/ADR-003-orientdb-cache.adoc @@ -0,0 +1,225 @@ += ADR-003: OrientDB Embedded Database + +Status: ✅ **Accepted** (with considerations for future evolution) + +Date: 2012-11-05 (Estimated - ContentStore implementation) + +== Problem Statement + +How should JBake store and query parsed content between pipeline stages? The system needs to: + +* Cache parsed documents (with metadata + HTML) +* Query documents by type, tags, date ranges +* Support pagination (skip/limit) +* Enable incremental builds (check if document changed) +* Provide fast lookups by URI +* Handle thousands of documents efficiently + +== Context + +Requirements for content storage: + +* **In-process**: No external database server (simple deployment) +* **Fast queries**: Tags page needs "all posts with tag X" +* **Schema flexibility**: Content metadata varies by document type +* **Embeddable**: Must work in Maven plugin, Gradle, standalone +* **Minimal setup**: No installation or configuration +* **Temporary storage**: Data discarded after generation (not persistent across runs) + +Alternatives available in 2012: + +* **In-memory collections** (List/Map): Simple but limited querying +* **H2 Database**: SQL, but requires schema definition +* **OrientDB**: Document database, schemaless, embeddable +* **MapDB**: Fast key-value store, limited query capabilities + +JBake's data is document-oriented (JSON-like metadata + body), making SQL databases awkward fit. + +== Decision + +**Use OrientDB as an embedded, in-memory document database for the ContentStore.** + +**Key characteristics:** + +* **Embedded mode**: Runs in-process, no server +* **Document model**: Stores content as JSON-like documents +* **SQL-like queries**: Familiar syntax for filtering/sorting +* **Schema-less**: No upfront schema definition needed +* **Indexes**: Automatic indexing for common queries +* **Graph capabilities**: (not used initially, but available) + +**ContentStore API:** + +```java +public class ContentStore { + private ODatabaseDocumentTx db; + + // Store parsed document + public void addDocument(Map doc); + + // Query by document type + public DocumentList getAllContent(String docType); + + // Query by tag + public DocumentList getPublishedContentByTag(String tag); + + // Pagination support + public void setLimit(int limit); + public void setStart(int start); + + // Incremental builds + public DocumentList getDocumentStatus(String uri); +} +``` + +**Data lifecycle:** + +1. **Startup**: Create in-memory OrientDB instance +2. **Crawl stage**: Store parsed documents +3. **Render stage**: Query documents for template data +4. **Shutdown**: Drop database (ephemeral) + +== Consequences + +=== Positive + +* ✅ **Rich querying**: SQL-like syntax for complex filters +* ✅ **No schema**: Flexible metadata (different per document type) +* ✅ **Fast**: In-memory, indexed lookups +* ✅ **Familiar**: SQL syntax developers already know +* ✅ **Pagination**: Built-in skip/limit support +* ✅ **Sorting**: ORDER BY for date-based archives + +=== Negative + +* ❌ **Heavy dependency**: OrientDB ~5MB, pulls in many transitive dependencies +* ❌ **API complexity**: Learning curve for OrientDB API +* ❌ **Lifecycle management**: Must carefully startup/shutdown database +* ❌ **Thread safety**: Requires `activateOnCurrentThread()` calls +* ❌ **Embedded only**: Can't easily inspect data during generation (no external tool) +* ❌ **Performance**: Overhead for small sites (< 100 pages) +* ❌ **Platform dependency**: Native libraries (JNA) cause issues on Apple Silicon (ARM64/M1/M2/M3), requires Rosetta 2 emulation or workarounds (see link:../chapters/11_technical_risks.adoc#RISK-001[RISK-001]) + +=== Neutral + +* ℹ️ In-memory only - data not persisted (by design) +* ℹ️ Graph features unused (document store sufficient) +* ℹ️ OrientDB's multi-model capabilities (graph/object) not exploited + +== Alternatives Considered + +=== Alternative 1: In-Memory Collections (List) + +**Description**: Store documents in `List>`, filter with Java streams. + +**Pugh Matrix Evaluation:** + +| Criterion | OrientDB (Baseline) | Collections | +|--------------------|---------------------|-------------| +| Simplicity | 0 | +3 | +| Dependencies | 0 | +3 | +| Query Complexity | 0 | -2 | +| Performance | 0 | -1 | +| Pagination | 0 | -2 | +| **Total Score** | **0** | **+1** | + +**Rejection rationale**: Close call. Collections simpler but querying becomes painful. Example: "Get published posts with tag 'java', sorted by date, limit 10" requires complex stream logic. No built-in pagination. Performance degrades with > 1000 documents (full scans). + +*Note: This alternative becomes more attractive in modern Java (8+) with streams. May revisit in ADR-007.* + +=== Alternative 2: H2 Embedded SQL Database + +**Description**: Use H2 with predefined schema, SQL queries. + +**Pugh Matrix Evaluation:** + +| Criterion | OrientDB (Baseline) | H2 SQL | +|--------------------|---------------------|--------| +| Schema Flexibility | 0 | -3 | +| Query Power | 0 | +1 | +| Setup | 0 | -2 | +| Dependencies | 0 | +1 | +| **Total Score** | **0** | **-3** | + +**Rejection rationale**: SQL databases require rigid schema. JBake's content metadata varies: blog posts have tags/categories, pages don't. Storing as JSON text loses query capabilities. Creating tables per document type is over-engineering. + +=== Alternative 3: MapDB (Key-Value Store) + +**Description**: Fast on-disk/memory key-value store with collections support. + +**Pugh Matrix Evaluation:** + +| Criterion | OrientDB (Baseline) | MapDB | +|--------------------|---------------------|-------| +| Simplicity | 0 | +2 | +| Query Capability | 0 | -3 | +| Dependencies | 0 | +2 | +| Sorted Collections | 0 | +1 | +| **Total Score** | **0** | **+2** | + +**Rejection rationale**: MapDB excellent for key-value, weak for queries. No built-in "find all documents with tag X". Would need to maintain separate indexes manually. OrientDB handles indexing automatically. + +== Implementation Notes + +**Database initialization:** + +```java +public ContentStore(final String type, String name) { + db = new ODatabaseDocumentTx(type + ":" + name); + if (!db.exists()) { + db.create(); + } + updateSchema(); // Create document classes +} +``` + +**Schema setup** (flexible document types): + +```java +public void updateSchema() { + for (String docType : DocumentTypes.getDocumentTypes()) { + OClass oClass = db.getMetadata().getSchema().getOrCreateClass(docType); + // OrientDB auto-creates fields on first insert + } +} +``` + +**Thread safety pattern:** + +```java +private void activateOnCurrentThread() { + if (!db.isActiveOnCurrentThread()) { + db.activateOnCurrentThread(); + } +} +``` + +**Critical**: OrientDB instances thread-local. Multi-threaded rendering requires `activateOnCurrentThread()` before each query. + +**Query examples:** + +```java +// Get all published posts +db.query(new OSQLSynchQuery( + "select * from post where status='published' order by date desc" +)); + +// Pagination +db.query(new OSQLSynchQuery( + "select * from post where status='published' limit 10 skip 20" +)); +``` + +== Related Decisions + +* link:ADR-001-pipeline-architecture.adoc[ADR-001]: Pipeline Architecture - ContentStore sits between pipeline stages +* link:ADR-006-incremental-builds.adoc[ADR-006]: Incremental Builds - Uses ContentStore to check document hashes + +== References + +* OrientDB Documentation: https://orientdb.com/docs/2.2.x/ +* ContentStore implementation: `jbake-core/src/main/java/org/jbake/app/ContentStore.java` +* Thread safety issues: Various GitHub issues around multi-threaded rendering +* Alternative discussion: https://github.com/jbake-org/jbake/issues/218 (consideration to replace OrientDB) +* Platform compatibility issues: Apple Silicon (ARM64) native library problems, documented in link:../chapters/11_technical_risks.adoc#RISK-001[Technical Risks - RISK-001] +* JNA (Java Native Access) dependency: Required for OrientDB native optimizations, source of platform issues diff --git a/docs/arc42/decisions/ADR-004-delegating-template-engine.adoc b/docs/arc42/decisions/ADR-004-delegating-template-engine.adoc new file mode 100644 index 000000000..aea19ace1 --- /dev/null +++ b/docs/arc42/decisions/ADR-004-delegating-template-engine.adoc @@ -0,0 +1,258 @@ += ADR-004: Delegating Template Engine Pattern + +Status: ✅ **Accepted** + +Date: 2013-04-10 (Estimated - multi-template support phase) + +== Problem Statement + +How should JBake support multiple template engines (Freemarker, Groovy, Thymeleaf, Jade, Pebble) without coupling the rendering pipeline to specific engine implementations? + +Users want to: + +* Choose their preferred template engine +* Use different engines for different templates +* Add new template engines without modifying core + +== Context + +Template rendering requirements: + +* **Multiple engines**: Support 5+ different template engines +* **File-based selection**: Template chosen by file extension (`.ftl`, `.groovy`, `.html`) +* **Consistent API**: Renderer doesn't care which engine used +* **Engine-specific features**: Each engine has unique capabilities (Groovy's GString, Thymeleaf's modes) +* **Error handling**: Template errors reported consistently + +Initial implementation (2012): + +* Only Freemarker supported +* `Renderer` class directly called Freemarker API +* Adding new engine required changing `Renderer` core logic + +Community requests (2013-2014): + +* Issue #42: Add Groovy template support +* Issue #89: Thymeleaf integration +* Issue #124: Jade templates + +Design patterns available: + +* **Strategy Pattern**: Different algorithms, same interface +* **Factory Pattern**: Create engine instances +* **Template Method**: Define skeleton, subclasses fill in +* **Delegation**: Forward requests to appropriate handler + +== Decision + +**Implement Delegating Template Engine pattern with SPI-based engine discovery.** + +**Architecture:** + +``` +┌──────────────────────────────────────┐ +│ Renderer (Pipeline) │ +└─────────────────┬────────────────────┘ + │ + ▼ +┌──────────────────────────────────────┐ +│ DelegatingTemplateEngine │ +│ - getEngine(fileExtension) │ +│ - renderDocument(template, model) │ +└─────────────────┬────────────────────┘ + │ + ▼ +┌──────────────────────────────────────┐ +│ TemplateEngines │ +│ Map │ +│ - ftl → FreemarkerEngine │ +│ - groovy → GroovyEngine │ +│ - html → ThymeleafEngine │ +└─────────────────┬────────────────────┘ + │ + ▼ + ┌─────────────┴─────────────┬───────────────┐ + ▼ ▼ ▼ +┌───────────┐ ┌──────────────┐ ┌──────────────┐ +│ Freemarker│ │ GroovyEngine │ │ Thymeleaf │ +│ Engine │ │ │ │ Engine │ +└───────────┘ └──────────────┘ └──────────────┘ +``` + +**Key classes:** + +1. **DelegatingTemplateEngine**: Facade for all engines + ```java + public class DelegatingTemplateEngine extends AbstractTemplateEngine { + private final TemplateEngines renderers; + + public void renderDocument(...) { + String ext = FileUtil.fileExt(templateFile); + AbstractTemplateEngine engine = renderers.getEngine(ext); + engine.renderDocument(...); + } + } + ``` + +2. **AbstractTemplateEngine**: Base class for all engines + ```java + public abstract class AbstractTemplateEngine { + protected final JBakeConfiguration config; + protected final ContentStore db; + + public abstract void renderDocument(Map model, + String templateName, + Writer writer); + } + ``` + +3. **TemplateEngines**: Registry (uses SPI from ADR-002) + ```java + public class TemplateEngines { + private final Map engines; + + public AbstractTemplateEngine getEngine(String extension) { + return engines.get(extension); + } + } + ``` + +**Engine selection logic:** + +* Template file: `post.ftl` → Freemarker engine +* Template file: `page.groovy` → Groovy engine +* Template file: `index.html` → Thymeleaf engine (configurable) + +== Consequences + +=== Positive + +* ✅ **Extensibility**: Add new engines without touching core +* ✅ **Flexibility**: Mix template engines in one project +* ✅ **Isolation**: Engine-specific code contained in engine class +* ✅ **Testability**: Mock engines for testing pipeline +* ✅ **SPI integration**: Leverages ADR-002 for discovery +* ✅ **Clear contracts**: `AbstractTemplateEngine` defines interface + +=== Negative + +* ❌ **Indirection**: Extra layer between Renderer and actual engine +* ❌ **Error context**: Stack traces deeper (delegating → engine → library) +* ❌ **Performance**: Small overhead from delegation (negligible in practice) +* ❌ **Mixed engines**: Sharing data between different engine types can be tricky + +=== Neutral + +* ℹ️ Each template file tied to one engine (can't mix in single file) +* ℹ️ Engine initialization happens once at startup (lazy loading possible) + +== Alternatives Considered + +=== Alternative 1: Direct Engine Integration + +**Description**: `Renderer` directly instantiates and calls each engine with if/else logic. + +**Pugh Matrix Evaluation:** + +| Criterion | Delegating (Baseline) | Direct Integration | +|--------------------|----------------------|--------------------| +| Extensibility | 0 | -3 | +| Maintainability | 0 | -3 | +| Simplicity | 0 | +1 | +| Coupling | 0 | -3 | +| **Total Score** | **0** | **-8** | + +**Rejection rationale**: Violates Open/Closed Principle. Adding engine requires modifying `Renderer`. Core code imports all engine libraries (heavy dependencies). if/else chain grows with each engine. Not scalable. + +=== Alternative 2: Template Method Pattern + +**Description**: `AbstractRenderer` with `abstract renderTemplate()`, subclasses for each engine. + +**Pugh Matrix Evaluation:** + +| Criterion | Delegating (Baseline) | Template Method | +|--------------------|----------------------|-----------------| +| Flexibility | 0 | -2 | +| Engine Reuse | 0 | -1 | +| Clear Hierarchy | 0 | +1 | +| Selection Logic | 0 | -2 | +| **Total Score** | **0** | **-4** | + +**Rejection rationale**: Requires separate Renderer subclass per engine (FreemarkerRenderer, GroovyRenderer, etc.). Complicates engine selection (factory needed). Engines can't be easily reused in other contexts. Delegation simpler. + +=== Alternative 3: Engine as Configuration + +**Description**: Users specify engine in `jbake.properties`, all templates use that engine. + +**Pugh Matrix Evaluation:** + +| Criterion | Delegating (Baseline) | Single Engine | +|--------------------|----------------------|---------------| +| Simplicity | 0 | +3 | +| Flexibility | 0 | -3 | +| User Experience | 0 | -2 | +| **Total Score** | **0** | **-2** | + +**Rejection rationale**: Too restrictive. Users can't mix engines. Migrating templates (Freemarker → Thymeleaf) requires converting all files at once. Existing themes tied to specific engine. Delegating pattern allows gradual migration. + +== Implementation Notes + +**Engine loading** (via SPI): + +Provider file: `META-INF/services/org.jbake.template.TemplateEngines.properties` +```properties +org.jbake.template.FreemarkerTemplateEngine=ftl +org.jbake.template.GroovyTemplateEngine=groovy,gsp,tpl +org.jbake.template.ThymeleafTemplateEngine=html +org.jbake.template.JadeTemplateEngine=jade +org.jbake.template.PebbleTemplateEngine=peb +``` + +**Graceful degradation:** + +```java +private static AbstractTemplateEngine tryLoadEngine(..., String engineClassName) { + try { + Class engineClass = Class.forName(engineClassName); + // Instantiate engine + } catch (ClassNotFoundException e) { + LOGGER.debug("Template engine {} not available on classpath", engineClassName); + return null; // Skip this engine + } +} +``` + +**Key design:** + +* Engines registered at startup +* Missing engines logged as debug (not error) +* Renderer fails only if selected engine not available +* Each engine is singleton (initialized once) + +**Model preparation:** + +`DelegatingTemplateEngine` prepares model data (adds config, db access) before delegating: + +```java +public void renderDocument(Map model, ...) { + // Extract model data + TemplateModel templateModel = new TemplateModel(); + templateModel.setContent(model); + + // Delegate to specific engine + AbstractTemplateEngine engine = renderers.getEngine(ext); + engine.renderDocument(templateModel.getModel(), ...); +} +``` + +== Related Decisions + +* link:ADR-002-spi-extensibility.adoc[ADR-002]: Java SPI - Used for engine discovery +* link:ADR-001-pipeline-architecture.adoc[ADR-001]: Pipeline Architecture - Delegating engine used in Render stage + +== References + +* DelegatingTemplateEngine: `jbake-core/src/main/java/org/jbake/template/DelegatingTemplateEngine.java` +* TemplateEngines registry: `jbake-core/src/main/java/org/jbake/template/TemplateEngines.java` +* Gang of Four Design Patterns: Strategy and Delegation patterns +* Issue #42: https://github.com/jbake-org/jbake/issues/42 (Groovy template support) diff --git a/docs/arc42/decisions/ADR-005-configuration-abstraction.adoc b/docs/arc42/decisions/ADR-005-configuration-abstraction.adoc new file mode 100644 index 000000000..dd6237dfa --- /dev/null +++ b/docs/arc42/decisions/ADR-005-configuration-abstraction.adoc @@ -0,0 +1,283 @@ += ADR-005: Configuration Abstraction Layer + +Status: ✅ **Accepted** + +Date: 2016-03-15 (Estimated - JBakeConfiguration interface introduction) + +== Problem Statement + +How should JBake manage configuration across different contexts (standalone, Maven plugin, Gradle plugin) while maintaining backwards compatibility and allowing future evolution? + +Challenges: + +* Configuration initially based on Apache Commons Configuration (mutable, complex API) +* Multiple access patterns: `config.getString()`, `config.getBoolean()`, type conversions +* Hard to test (concrete class, no interface) +* Tight coupling to Apache Commons Configuration +* Plugins need different configuration sources (POM, build.gradle, jbake.properties) + +== Context + +Configuration evolution: + +**Phase 1 (2012-2015)**: Direct `CompositeConfiguration` usage +```java +public class Oven { + public Oven(CompositeConfiguration config) { + String output = config.getString("destination.folder"); + } +} +``` + +Problems: + +* Core classes depend on Apache Commons Configuration +* No type safety (everything returns Object or String) +* Magic strings everywhere (`"destination.folder"`) +* Hard to mock for testing +* Configuration changes require changes throughout codebase + +**Phase 2 (2016+)**: Need for abstraction + +* Maven plugin wants different config sources +* Testing requires mocking configuration +* Type safety needed (getDestinationFolder() vs getString("destination.folder")) +* Desire to potentially replace Apache Commons Configuration + +Design goals: + +* **Encapsulation**: Hide configuration library from core +* **Type safety**: Dedicated methods for each property +* **Testability**: Interface allows mocking +* **Documentation**: Each property documented in code +* **Evolution**: Can change underlying implementation + +== Decision + +**Introduce `JBakeConfiguration` interface with `DefaultJBakeConfiguration` implementation, hiding Apache Commons Configuration behind abstraction.** + +**Architecture:** + +``` +┌────────────────────────────────────────┐ +│ Core Classes (Oven, Crawler, etc) │ +│ - Depend only on interface │ +└──────────────────┬─────────────────────┘ + │ + ▼ +┌────────────────────────────────────────┐ +│ JBakeConfiguration interface │ +│ + getDestinationFolder(): File │ +│ + getSourceFolder(): File │ +│ + getTemplateFolder(): File │ +│ + getSiteHost(): String │ +│ + get(String): Object │ +└──────────────────┬─────────────────────┘ + │ + ▼ +┌────────────────────────────────────────┐ +│ DefaultJBakeConfiguration │ +│ - Wraps CompositeConfiguration │ +│ - Implements all typed getters │ +└──────────────────┬─────────────────────┘ + │ + ▼ +┌────────────────────────────────────────┐ +│ Apache Commons Configuration │ +│ (implementation detail) │ +└────────────────────────────────────────┘ +``` + +**Key interfaces:** + +```java +public interface JBakeConfiguration { + // Typed accessors + File getSourceFolder(); + File getDestinationFolder(); + File getTemplateFolder(); + File getAssetFolder(); + + String getSiteHost(); + boolean getRenderTags(); + int getPostsPerPage(); + + // Generic access (backwards compatibility) + Object get(String key); + String getString(String key); + + // Query capabilities + Iterator getKeys(); +} +``` + +**Factory pattern:** + +```java +public class JBakeConfigurationFactory { + public static JBakeConfiguration createDefaultConfiguration( + File source, File destination, CompositeConfiguration config) { + return new DefaultJBakeConfiguration(source, destination, config); + } + + public static JBakeConfiguration createDefaultConfiguration( + File source, File destination) { + // Load jbake.properties from source folder + } +} +``` + +**Property enumeration:** + +```java +public abstract class PropertyList { + public static final Property SOURCE_FOLDER = new Property( + "source.folder", + "Source folder for content files" + ); + + public static final Property DESTINATION_FOLDER = new Property( + "destination.folder", + "Output folder for generated site" + ); + + // ... 50+ properties +} +``` + +== Consequences + +=== Positive + +* ✅ **Decoupling**: Core independent of configuration library +* ✅ **Type safety**: `getDestinationFolder()` returns `File`, not `Object` +* ✅ **Discoverability**: IDE autocomplete shows available properties +* ✅ **Documentation**: Javadoc on each method +* ✅ **Testability**: Easy to mock `JBakeConfiguration` +* ✅ **Evolution**: Can replace implementation without API changes +* ✅ **Plugin flexibility**: Plugins can implement interface differently + +=== Negative + +* ❌ **Verbosity**: Many getter methods (50+ properties) +* ❌ **Maintenance**: Adding property requires updating interface, implementation, PropertyList +* ❌ **Backwards compatibility**: Still exposes `get(String)` for old code +* ❌ **Partial abstraction**: Some code still uses `CompositeConfiguration` directly + +=== Neutral + +* ℹ️ Interface doesn't hide everything (still exposes some config details) +* ℹ️ Default implementation still uses Apache Commons Configuration (could change) + +== Alternatives Considered + +=== Alternative 1: Keep Direct Configuration Usage + +**Description**: Continue using `CompositeConfiguration` directly throughout codebase. + +**Pugh Matrix Evaluation:** + +| Criterion | Abstraction (Baseline) | Direct Usage | +|--------------------|------------------------|--------------| +| Simplicity | 0 | +2 | +| Type Safety | 0 | -3 | +| Testability | 0 | -2 | +| Coupling | 0 | -3 | +| **Total Score** | **0** | **-6** | + +**Rejection rationale**: Tight coupling to Apache Commons Configuration. Testing requires real configuration files. No type safety. Hard to evolve. Rejected for maintainability reasons. + +=== Alternative 2: Property Classes (TypeSafe Config Style) + +**Description**: Configuration as immutable objects: `class SourceFolder { File path; }` + +**Pugh Matrix Evaluation:** + +| Criterion | Interface (Baseline) | Property Classes | +|--------------------|----------------------|------------------| +| Type Safety | 0 | +1 | +| Immutability | 0 | +2 | +| Backwards Compat | 0 | -3 | +| Complexity | 0 | +1 | +| **Total Score** | **0** | **+1** | + +**Rejection rationale**: Close call. Property classes offer better type safety and immutability. However, backwards compatibility is critical. Existing code uses string-based access. Migration would be massive. Interface approach allows gradual migration. + +=== Alternative 3: Builder Pattern with Fluent API + +**Description**: `new JBakeConfig().withSourceFolder(src).withDestination(dest).build()` + +**Pugh Matrix Evaluation:** + +| Criterion | Interface (Baseline) | Builder Pattern | +|--------------------|----------------------|-----------------| +| Usability | 0 | +2 | +| Testability | 0 | +1 | +| File-based Config | 0 | -2 | +| Backwards Compat | 0 | -3 | +| **Total Score** | **0** | **-2** | + +**Rejection rationale**: Builder great for programmatic configuration (tests, plugins). Poor fit for file-based config (jbake.properties). Users expect properties files, not Java builders. Could add builder alongside interface in future. + +== Implementation Notes + +**Migration strategy:** + +1. ✅ Create `JBakeConfiguration` interface +2. ✅ Implement `DefaultJBakeConfiguration` wrapping existing config +3. ✅ Update core classes to use interface +4. ⏳ Deprecate direct `CompositeConfiguration` usage +5. ⏳ Eventually remove `CompositeConfiguration` from public API + +**Typed getter example:** + +```java +@Override +public File getDestinationFolder() { + return getAsFolder(DESTINATION_FOLDER.getKey()); +} + +private File getAsFolder(String key) { + String path = config.getString(key); + return path != null ? new File(path) : null; +} +``` + +**Backwards compatibility:** + +```java +@Override +public Object get(String key) { + return config.getProperty(key); +} + +@Override +public String getString(String key) { + return config.getString(key); +} +``` + +**Testing with mocks:** + +```java +@Test +public void testCrawler() { + JBakeConfiguration mockConfig = mock(JBakeConfiguration.class); + when(mockConfig.getSourceFolder()).thenReturn(testFolder); + + Crawler crawler = new Crawler(mockConfig); + // Test without real configuration file +} +``` + +== Related Decisions + +* link:ADR-001-pipeline-architecture.adoc[ADR-001]: Pipeline Architecture - Configuration passed through pipeline +* link:ADR-007-deprecation-strategy.adoc[ADR-007]: Deprecation Strategy - Old config methods deprecated gradually + +== References + +* JBakeConfiguration interface: `jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfiguration.java` +* DefaultJBakeConfiguration: `jbake-core/src/main/java/org/jbake/app/configuration/DefaultJBakeConfiguration.java` +* PropertyList enum: `jbake-core/src/main/java/org/jbake/app/configuration/PropertyList.java` +* Apache Commons Configuration: https://commons.apache.org/proper/commons-configuration/ diff --git a/docs/arc42/decisions/ADR-006-incremental-builds.adoc b/docs/arc42/decisions/ADR-006-incremental-builds.adoc new file mode 100644 index 000000000..973d62cb3 --- /dev/null +++ b/docs/arc42/decisions/ADR-006-incremental-builds.adoc @@ -0,0 +1,281 @@ += ADR-006: SHA1-based Incremental Builds + +Status: ✅ **Accepted** + +Date: 2013-08-20 (Estimated - performance optimization phase) + +== Problem Statement + +How can JBake avoid regenerating unchanged files to improve build performance? + +Problem scenarios: + +* Blog with 500 posts: full rebuild takes 30+ seconds +* During development: changing one post rebuilds everything +* CI builds: repeatedly processing same content wastes time +* Large sites (1000+ pages): full builds become impractical + +Requirements: + +* Detect changed content files +* Detect changed templates +* Detect changed configuration +* Skip processing for unchanged files +* Ensure correctness (no stale output) + +== Context + +Performance bottlenecks: + +* **Parsing**: Markdown/AsciiDoc parsing is CPU-intensive +* **Rendering**: Template rendering for hundreds of pages +* **I/O**: Writing thousands of HTML files + +Observation: Most builds process < 5% changed files. + +Incremental build strategies: + +* **Timestamp-based**: Compare file modification times +* **Content hashing**: Hash file contents (SHA1, MD5) +* **No caching**: Always regenerate everything + +Challenges: + +* Cross-file dependencies: Tag pages depend on all posts with that tag +* Template changes: Affect all files using that template +* Configuration changes: May affect rendering logic + +Static site generators comparison (2013): + +* **Jekyll**: Incremental via `--incremental` (timestamp-based) +* **Hugo**: Very fast, regenerates everything (Go performance) +* **Middleman**: No incremental support + +== Decision + +**Implement SHA1-based incremental builds at the Crawler stage, with template and configuration change detection.** + +**Strategy:** + +1. **Content hashing**: Calculate SHA1 of each content file +2. **Template hashing**: Calculate SHA1 of all template files +3. **Configuration tracking**: Detect jbake.properties changes +4. **Database comparison**: Check if SHA1 exists in ContentStore +5. **Skip unchanged**: Don't parse files with matching SHA1 +6. **Force regeneration**: If templates/config changed, rebuild all + +**Implementation:** + +``` +┌────────────────────────────────────────┐ +│ Crawler Stage │ +└────────────────┬───────────────────────┘ + │ + ▼ + ┌───────────────────────┐ + │ For each content file│ + └───────────┬───────────┘ + │ + ▼ + ┌───────────────────────┐ + │ Calculate SHA1 hash │ + └───────────┬───────────┘ + │ + ▼ + ┌───────────────────────┐ + │ Check ContentStore │ + │ for existing SHA1 │ + └───────┬───────────────┘ + │ + ┌───────┴────────┐ + │ │ + SHA1 match? SHA1 new/different? + │ │ + ▼ ▼ + Skip parsing Parse & store + Use cached Update SHA1 +``` + +**SHA1 calculation:** + +```java +public class Crawler { + private String calculateHash(File file) throws IOException { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + try (InputStream is = new FileInputStream(file)) { + byte[] buffer = new byte[8192]; + int read; + while ((read = is.read(buffer)) > 0) { + md.update(buffer, 0, read); + } + } + byte[] digest = md.digest(); + return DatatypeConverter.printHexBinary(digest); + } +} +``` + +**Change detection:** + +```java +private boolean hasFileBeenModified(File file, String sha1) { + DocumentList docs = db.getDocumentStatus(file.getPath()); + if (docs.isEmpty()) { + return true; // New file + } + String storedSha1 = (String) docs.get(0).get("sha1"); + return !sha1.equals(storedSha1); // Compare hashes +} +``` + +**Template change detection:** + +* Calculate hash of all template files combined +* Store in ContentStore metadata +* If template hash changes: clear cache, rebuild all +* Ensures template changes propagate to all content + +**Configuration change:** + +* Monitor jbake.properties modification time +* If changed: invalidate cache +* Ensures config changes (site URL, etc.) apply everywhere + +== Consequences + +=== Positive + +* ✅ **Massive speedup**: 30s → 2s for single-file changes +* ✅ **Accuracy**: SHA1 detects actual content changes (not just timestamps) +* ✅ **Developer experience**: Fast feedback during writing +* ✅ **CI efficiency**: Skip unchanged files in repeated builds +* ✅ **Correctness**: Template/config changes force full rebuild + +=== Negative + +* ❌ **Initial overhead**: First build still slow (no cache) +* ❌ **Hash calculation**: Extra I/O to read files for hashing +* ❌ **Memory usage**: Store SHA1 for every file in database +* ❌ **Cross-dependencies**: Tag/archive pages always regenerate (depend on all posts) +* ❌ **Cache invalidation**: No smart dependency tracking (template change = rebuild all) + +=== Neutral + +* ℹ️ SHA1 sufficient (collision probability negligible for this use case) +* ℹ️ Cache is in-memory only (not persistent across JBake invocations) + +== Alternatives Considered + +=== Alternative 1: Timestamp-Based Incremental Builds + +**Description**: Compare file modification times instead of content hashes. + +**Pugh Matrix Evaluation:** + +| Criterion | SHA1 (Baseline) | Timestamp | +|--------------------|-----------------|-----------| +| Accuracy | 0 | -2 | +| Performance | 0 | +1 | +| Simplicity | 0 | +2 | +| Reliability | 0 | -3 | +| **Total Score** | **0** | **-2** | + +**Rejection rationale**: Timestamps unreliable. Examples: +* Git checkout resets timestamps → false positives (rebuild unchanged) +* File copy preserves content but changes timestamp +* Clock skew in CI environments +* Touch command breaks cache + +SHA1 detects actual content changes. + +=== Alternative 2: Persistent Cache Between Builds + +**Description**: Save ContentStore to disk, reuse across JBake invocations. + +**Pugh Matrix Evaluation:** + +| Criterion | In-Memory (Baseline) | Persistent Cache | +|--------------------|----------------------|------------------| +| Cross-run Speed | 0 | +3 | +| Complexity | 0 | -2 | +| Reliability | 0 | -1 | +| Disk I/O | 0 | -1 | +| **Total Score** | **0** | **-1** | + +**Rejection rationale**: Marginal benefit. JBake runs are seconds (not minutes), so cache invalidation/loading overhead offsets gains. Persistent cache adds: +* Corruption risk (incomplete shutdown) +* Version migration (schema changes) +* Disk space usage +* More failure modes + +In-memory sufficient for typical workflows. + +=== Alternative 3: No Incremental Builds + +**Description**: Always regenerate everything, optimize parsing/rendering instead. + +**Pugh Matrix Evaluation:** + +| Criterion | Incremental (Baseline) | Full Rebuild | +|--------------------|------------------------|--------------| +| Simplicity | 0 | +3 | +| Development Speed | 0 | -3 | +| Correctness | 0 | +2 | +| Large Sites | 0 | -3 | +| **Total Score** | **0** | **-1** | + +**Rejection rationale**: Simple but painful for large sites. Hugo (Go-based) regenerates everything in < 1s for 1000s pages via extreme optimization. JVM startup + library overhead makes JBake slower. Incremental builds essential for good developer experience. + +== Implementation Notes + +**Crawler integration:** + +```java +public void crawl() { + // Before processing + String templateHash = calculateTemplateHash(); + boolean templatesChanged = hasTemplateChanged(templateHash); + + if (templatesChanged) { + db.drop(); // Clear cache, force full rebuild + } + + for (File file : contentFiles) { + String sha1 = calculateHash(file); + + if (!templatesChanged && !hasFileBeenModified(file, sha1)) { + // Skip processing, reuse cached content + continue; + } + + // Parse and process + Map content = parseFile(file); + content.put("sha1", sha1); + db.addDocument(content); + } +} +``` + +**Limitations:** + +* **Tag/archive regeneration**: Always rebuilt (depend on all content) +* **Asset changes**: Not tracked (always copied) +* **No dependency graph**: Can't detect "post A links to post B" changes + +**Future enhancements:** + +* Persistent cache (addressed in ADR-007 discussions) +* Smarter dependency tracking (per-template cache) +* Parallel hashing (multi-threaded I/O) + +== Related Decisions + +* link:ADR-001-pipeline-architecture.adoc[ADR-001]: Pipeline Architecture - Incremental builds optimize Crawler stage +* link:ADR-003-orientdb-cache.adoc[ADR-003]: OrientDB Database - ContentStore used for SHA1 storage + +== References + +* SHA1 algorithm: https://en.wikipedia.org/wiki/SHA-1 +* Crawler implementation: `jbake-core/src/main/java/org/jbake/app/Crawler.java` +* ContentStore SHA1 handling: `jbake-core/src/main/java/org/jbake/app/ContentStore.java` +* Performance benchmarks: https://github.com/jbake-org/jbake/issues/156 diff --git a/docs/arc42/decisions/ADR-007-deprecation-strategy.adoc b/docs/arc42/decisions/ADR-007-deprecation-strategy.adoc new file mode 100644 index 000000000..a23c58fc4 --- /dev/null +++ b/docs/arc42/decisions/ADR-007-deprecation-strategy.adoc @@ -0,0 +1,274 @@ += ADR-007: Backwards Compatibility via Deprecation + +Status: ✅ **Accepted** + +Date: 2017-06-10 (Estimated - major refactoring period) + +== Problem Statement + +How should JBake evolve its API and features while maintaining compatibility with existing projects, themes, and plugins? + +Tensions: + +* **Innovation**: Need to modernize codebase, fix design issues +* **Stability**: Users expect projects to work across JBake versions +* **Ecosystem**: Themes and plugins depend on current API +* **Technical debt**: Some early decisions need revision (e.g., OrientDB, direct CompositeConfiguration) + +Examples requiring evolution: + +* Replace OrientDB with lighter storage (ADR-003) +* Hide Apache Commons Configuration (ADR-005) +* Modernize for Java 8+ features (lambdas, streams) +* Deprecated Freemarker methods +* Old configuration keys + +Failure scenarios: + +* Breaking changes without notice → angry users +* No deprecation warnings → surprise breakage +* Keeping technical debt forever → unmaintainable + +== Context + +JBake's user base (2017): + +* Hundreds of sites in production +* Dozens of community themes +* Multiple plugins (Maven, Gradle) +* Documentation/tutorials across the web + +Compatibility spectrum: + +``` +API Stability Level: +├── Public API (contracts): Must maintain +├── Extension Points (SPI): Careful evolution +├── Internal Classes: Can change +└── Private Methods: Free to change +``` + +Industry practices: + +* **Semantic Versioning**: MAJOR.MINOR.PATCH (break.feature.fix) +* **Deprecation cycles**: Mark → Warn → Remove +* **Java's approach**: Deprecate for years before removal +* **Spring Framework**: Deprecate for 2+ major versions + +JBake challenges: + +* No clear "public API" definition initially +* Users import internal classes +* Themes rely on model structure +* Configuration keys as contract + +== Decision + +**Adopt a deprecation-first strategy with semantic versioning and multi-version deprecation cycles.** + +**Policy:** + +1. **Semantic versioning**: MAJOR.MINOR.PATCH + - **MAJOR**: Breaking changes (only after deprecation) + - **MINOR**: New features, deprecations + - **PATCH**: Bug fixes + +2. **Deprecation cycle**: 2 major versions minimum + ``` + v2.5: Introduce new API, deprecate old + v2.6-2.9: Both APIs work, warnings issued + v3.0: Remove deprecated API + ``` + +3. **Documentation requirements**: + - `@Deprecated` annotation with Javadoc + - Migration guide in CHANGELOG + - Replacement clearly documented + +4. **Gradual migration**: + - Old and new APIs coexist + - Adapter pattern for compatibility + - Clear migration paths + +**Classification:** + +| Component | Stability | Evolution Strategy | +|-----------|-----------|-------------------| +| Configuration keys | Stable | Alias old → new | +| JBakeConfiguration interface | Stable | Add methods, deprecate old | +| ContentStore methods | Internal | Can change with deprecation | +| Template model structure | Stable | Extend, don't break | +| SPI interfaces | Stable | Version-aware loading | + +**Deprecation examples:** + +```java +// Old method (deprecated) +/** + * @deprecated Use {@link #DelegatingTemplateEngine(ContentStore, JBakeConfiguration)} instead. + * Will be removed in JBake 3.0. + */ +@Deprecated +public DelegatingTemplateEngine(CompositeConfiguration config, + ContentStore db, + File destination, + File templatesPath) { + // Implementation using adapter to new constructor +} + +// New method +public DelegatingTemplateEngine(ContentStore db, JBakeConfiguration config) { + // Modern API +} +``` + +**Configuration aliasing:** + +```properties +# Old key (still works) +destination.folder=/output + +# New key (preferred) +jbake.destination.folder=/output + +# Internally: old → new mapping maintained +``` + +== Consequences + +=== Positive + +* ✅ **User confidence**: Projects continue working +* ✅ **Ecosystem stability**: Themes/plugins have time to update +* ✅ **Clear expectations**: Semantic versioning communicates risk +* ✅ **Gradual migration**: No forced big-bang upgrades +* ✅ **Innovation enabled**: Can evolve without fear +* ✅ **Warning visibility**: Users know what to fix + +=== Negative + +* ❌ **Maintenance burden**: Support old + new APIs simultaneously +* ❌ **Code bloat**: Adapter code for compatibility +* ❌ **Testing overhead**: Test both old and new paths +* ❌ **Slower evolution**: Can't remove bad decisions quickly +* ❌ **User confusion**: Multiple ways to do same thing + +=== Neutral + +* ℹ️ Major versions rare (years between) +* ℹ️ Some users never see deprecation warnings (ignore logs) + +== Alternatives Considered + +=== Alternative 1: Breaking Changes Without Deprecation + +**Description**: Make breaking changes immediately, bump major version, document in CHANGELOG. + +**Pugh Matrix Evaluation:** + +| Criterion | Deprecation (Baseline) | Immediate Break | +|--------------------|------------------------|-----------------| +| Innovation Speed | 0 | +3 | +| User Experience | 0 | -3 | +| Maintenance | 0 | +2 | +| Ecosystem Health | 0 | -3 | +| **Total Score** | **0** | **-1** | + +**Rejection rationale**: Fast evolution but painful upgrades. Example: JBake 2.5 → 2.6 breaks all projects. Users stuck on old versions. Themes abandoned. Community fragments. Not worth speed gain. + +=== Alternative 2: Freeze API Forever + +**Description**: Declare current API stable, never break compatibility. + +**Pugh Matrix Evaluation:** + +| Criterion | Deprecation (Baseline) | API Freeze | +|--------------------|------------------------|------------| +| Stability | 0 | +3 | +| Technical Debt | 0 | -3 | +| Innovation | 0 | -3 | +| Code Quality | 0 | -2 | +| **Total Score** | **0** | **-5** | + +**Rejection rationale**: Stability at cost of progress. Early mistakes (OrientDB dependency, mutable config) become permanent. Can't adopt new Java features. Codebase ossifies. Eventually abandoned for modern alternatives. + +=== Alternative 3: Parallel Versioning (v2 & v3 simultaneously) + +**Description**: Maintain two separate codebases: stable v2 branch, experimental v3. + +**Pugh Matrix Evaluation:** + +| Criterion | Deprecation (Baseline) | Parallel Versions | +|--------------------|------------------------|-------------------| +| User Choice | 0 | +2 | +| Maintenance Burden | 0 | -3 | +| Bug Fixes | 0 | -2 | +| Resource Cost | 0 | -3 | +| **Total Score** | **0** | **-6** | + +**Rejection rationale**: Double the maintenance. Bug fixes need backporting. Security patches to multiple versions. Small team can't sustain. Confuses users ("which version?"). Deprecation achieves same goal with single codebase. + +== Implementation Notes + +**Deprecation checklist:** + +1. ✅ Add `@Deprecated` annotation +2. ✅ Write Javadoc with: + - Reason for deprecation + - Replacement API + - Target removal version +3. ✅ Add entry to CHANGELOG +4. ✅ Update documentation/examples +5. ✅ Create migration guide if complex +6. ✅ Add warning logs (if applicable) + +**Example changelog entry:** + +```markdown +## [2.6.0] - 2017-06-15 + +### Deprecated +- `DelegatingTemplateEngine(CompositeConfiguration, ContentStore, File, File)` + constructor. Use `DelegatingTemplateEngine(ContentStore, JBakeConfiguration)` + instead. Old constructor will be removed in JBake 3.0. + Migration: Replace `new DelegatingTemplateEngine(config, db, dest, templates)` + with `new DelegatingTemplateEngine(db, jbakeConfig)`. +``` + +**Adapter pattern example:** + +```java +@Deprecated +public DelegatingTemplateEngine(CompositeConfiguration config, + ContentStore db, + File destination, + File templatesPath) { + // Adapt old parameters to new API + JBakeConfiguration jbakeConfig = new DefaultJBakeConfiguration( + templatesPath.getParentFile(), destination, config); + + // Delegate to new constructor + this(db, jbakeConfig); +} +``` + +**Version timeline example:** + +| Version | Date | Action | +|---------|------|--------| +| 2.5.0 | 2016-03 | Introduce JBakeConfiguration interface | +| 2.6.0 | 2017-06 | Deprecate CompositeConfiguration constructors | +| 2.7.0-2.9.x | 2017-2019 | Both APIs supported, warnings | +| 3.0.0 | 2020+ | Remove deprecated CompositeConfiguration usage | + +== Related Decisions + +* link:ADR-005-configuration-abstraction.adoc[ADR-005]: Configuration Abstraction - Example of gradual migration +* link:ADR-008-java-version-policy.adoc[ADR-008]: Java Version Policy - Balances compatibility vs modernization + +== References + +* Semantic Versioning: https://semver.org/ +* Java Deprecation Guide: https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/deprecation/deprecation.html +* JBake CHANGELOG: `CHANGELOG.md` +* Effective Java (Joshua Bloch): Chapter on API design and deprecation diff --git a/docs/arc42/decisions/ADR-008-java-version-policy.adoc b/docs/arc42/decisions/ADR-008-java-version-policy.adoc new file mode 100644 index 000000000..d43403910 --- /dev/null +++ b/docs/arc42/decisions/ADR-008-java-version-policy.adoc @@ -0,0 +1,268 @@ += ADR-008: Java Version Support Policy + +Status: ✅ **Accepted** + +Date: 2019-09-20 (Estimated - Java 8+ transition period) + +== Problem Statement + +Which Java versions should JBake support, and when should support for older versions be dropped? + +Tensions: + +* **Modern features**: Java 8+ offers lambdas, streams, Optional, time API +* **User base**: Many organizations stuck on Java 7 or even Java 6 +* **Library dependencies**: Libraries increasingly require Java 8+ +* **Maintenance burden**: Supporting old Java versions limits code modernization +* **Platform evolution**: Java release cadence changed (6-month cycles since Java 9) + +== Context + +Java version landscape (2019): + +* **Java 6**: EOL 2013, but still in production at banks/government +* **Java 7**: EOL 2015, declining but significant install base +* **Java 8**: LTS, released 2014, dominant version in enterprise +* **Java 9-10**: Short-term releases (already EOL) +* **Java 11**: LTS (Sep 2018), slow adoption +* **Java 12-13**: Current short-term releases + +JBake history: + +* **2012-2015**: Targeted Java 6 compatibility +* **2015-2017**: Java 7 minimum +* **2017+**: Pressure to adopt Java 8 features + +Dependency constraints: + +* **Gradle**: Requires Java 8+ since Gradle 5 +* **Maven**: Supports Java 7 but plugins moving to 8 +* **OrientDB**: 2.x supports Java 7, 3.x requires Java 8 +* **Thymeleaf 3**: Requires Java 8+ +* **Asciidoctor**: Requires Java 8+ + +User surveys (2019): + +* 70% using Java 8+ +* 20% on Java 7 +* 10% on Java 6 + +== Decision + +**Adopt a "Current LTS + Previous LTS" support policy, dropping older versions with major releases.** + +**Policy:** + +1. **Minimum version**: Java 8 (as of JBake 2.6.0+) +2. **Target version**: Current LTS (Java 11 recommended) +3. **Drop policy**: Drop support with major version bump (JBake 3.0 → Java 11) +4. **Compile target**: Bytecode for minimum version +5. **CI testing**: Test on minimum + current LTS + +**Rationale for Java 8 minimum:** + +* **Ecosystem**: Critical dependencies require Java 8 +* **Language features**: Lambdas, streams, Optional improve code quality +* **Market adoption**: 70%+ users already on Java 8+ +* **Oracle EOL**: Java 7 public updates ended 2015 + +**Future evolution:** + +| JBake Version | Min Java | Reason | +|---------------|----------|--------| +| 2.5.x | Java 7 | Legacy support | +| 2.6.x - 2.9.x | Java 8 | Current policy | +| 3.0 (future) | Java 11 | Next LTS transition | +| 4.0 (future) | Java 17 | Next LTS (2021) | + +**Deprecation notice:** + +``` +JBake 2.5.x: "Java 7 support will be dropped in JBake 2.6.0" +JBake 2.6.0: Require Java 8, document in CHANGELOG/README +JBake 2.9.x: "Java 8 support will be dropped in JBake 3.0" +JBake 3.0: Require Java 11 +``` + +**Build configuration:** + +```gradle +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +test { + // Test on multiple Java versions in CI + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(8) + } +} +``` + +== Consequences + +=== Positive + +* ✅ **Modern code**: Use lambdas, streams, Optional, java.time +* ✅ **Library compatibility**: Access to modern dependencies +* ✅ **Performance**: Benefit from JVM improvements +* ✅ **Developer experience**: Better IDE support, tooling +* ✅ **Reduced complexity**: Don't maintain Java 6/7 workarounds +* ✅ **Clear expectations**: Users know requirements + +=== Negative + +* ❌ **User exclusion**: 20% on Java 7 must upgrade or stay on old JBake +* ❌ **Enterprise friction**: Some orgs slow to upgrade Java +* ❌ **Support burden**: Maintain old version for legacy users? +* ❌ **Migration effort**: Users must test on new Java version + +=== Neutral + +* ℹ️ Java 8 itself is very stable (LTS until 2030 for some vendors) +* ℹ️ Can compile with Java 11 targeting Java 8 bytecode + +== Alternatives Considered + +=== Alternative 1: Stay on Java 7 Forever + +**Description**: Never drop Java 7 support, maintain compatibility indefinitely. + +**Pugh Matrix Evaluation:** + +| Criterion | Java 8 Min (Baseline) | Java 7 Forever | +|--------------------|------------------------|----------------| +| User Coverage | 0 | +1 | +| Code Modernization | 0 | -3 | +| Dependency Access | 0 | -3 | +| Maintenance | 0 | -2 | +| **Total Score** | **0** | **-7** | + +**Rejection rationale**: Technical stagnation. Can't use modern libraries (Thymeleaf 3, Asciidoctor, etc.). Code stuck with pre-Java-8 patterns (anonymous inner classes, no Optional). Eventually unmaintainable. Java 7 is 4+ years EOL. + +=== Alternative 2: Jump to Java 11 Immediately + +**Description**: Skip Java 8, require Java 11 minimum in JBake 2.6.0. + +**Pugh Matrix Evaluation:** + +| Criterion | Java 8 Min (Baseline) | Java 11 Min | +|--------------------|------------------------|-------------| +| Code Modernization | 0 | +2 | +| User Coverage | 0 | -2 | +| Ecosystem Maturity | 0 | -1 | +| Adoption Risk | 0 | -2 | +| **Total Score** | **0** | **-3** | + +**Rejection rationale**: Too aggressive. Java 11 adoption lower in 2019 (enterprise lag). 70% on Java 8, only 10% on 11+. Would exclude most users. Java 8 → 11 doesn't add killer features for JBake's needs. Better to wait. + +=== Alternative 3: Multi-version Builds (Java 8 & 11) + +**Description**: Publish separate artifacts: `jbake-core-java8` and `jbake-core-java11`. + +**Pugh Matrix Evaluation:** + +| Criterion | Single Version (Baseline) | Multi-version | +|--------------------|---------------------------|---------------| +| User Choice | 0 | +2 | +| Maintenance | 0 | -3 | +| Build Complexity | 0 | -3 | +| Confusion | 0 | -2 | +| **Total Score** | **0** | **-6** | + +**Rejection rationale**: Massive complexity. Need separate: +* Build pipelines +* Artifact publishing +* Dependency management +* Documentation +* Bug testing (which version?) + +Small team can't sustain. Gradle/Maven don't do this; neither should JBake. + +== Implementation Notes + +**Gradle configuration:** + +```gradle +plugins { + id 'java-library' +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +// Enforce at compile time +tasks.withType(JavaCompile) { + options.compilerArgs += ['-Xlint:all', '-Werror'] +} +``` + +**CI testing matrix:** + +```yaml +# GitHub Actions example +strategy: + matrix: + java: [8, 11, 17] +steps: + - uses: actions/setup-java@v2 + with: + java-version: ${{ matrix.java }} + - run: ./gradlew test +``` + +**Runtime check:** + +```java +public class JBake { + static { + String version = System.getProperty("java.version"); + if (!isJava8OrHigher()) { + throw new UnsupportedClassVersionError( + "JBake requires Java 8 or higher. Found: " + version); + } + } +} +``` + +**Documentation updates:** + +1. README: "Requirements: Java 8+" +2. Website installation page +3. Build tool plugin docs (Maven, Gradle) +4. CHANGELOG: Breaking change notice + +**Migration guide for users:** + +```markdown +## Upgrading to JBake 2.6.0+ + +**Action Required**: Java 8 or higher is now required. + +### Check your Java version: +```bash +java -version +``` + +### If on Java 7 or lower: +1. Upgrade to Java 8 (or 11 LTS) +2. Update build tools (Maven 3.5+, Gradle 4.10+) +3. Test your site generation + +### If you cannot upgrade Java: +- Stay on JBake 2.5.x (maintenance only) +- Security updates backported on request +``` + +== Related Decisions + +* link:ADR-007-deprecation-strategy.adoc[ADR-007]: Deprecation Strategy - How we communicate breaking changes +* link:ADR-005-configuration-abstraction.adoc[ADR-005]: Configuration Abstraction - Uses Java 8 features (Optional, etc.) + +== References + +* Java SE Support Roadmap: https://www.oracle.com/java/technologies/java-se-support-roadmap.html +* JBake Installation Docs: https://jbake.org/docs/ +* Gradle Java compatibility: https://docs.gradle.org/current/userguide/building_java_projects.html#sec:java_cross_compilation +* Survey data: Community discussions on GitHub/Google Groups diff --git a/docs/arc42/decisions/README.adoc b/docs/arc42/decisions/README.adoc new file mode 100644 index 000000000..d53999f03 --- /dev/null +++ b/docs/arc42/decisions/README.adoc @@ -0,0 +1,122 @@ += Architecture Decision Records + +This directory contains Architecture Decision Records (ADRs) documenting significant architectural decisions in JBake. + +== ADR Timeline & Evolution Phases + +=== Phase 1: Foundation (2012-2014) - "Get it Working" + +* link:ADR-001-pipeline-architecture.adoc[ADR-001]: Pipeline Architecture +* link:ADR-002-spi-extensibility.adoc[ADR-002]: Java SPI for Extensibility +* link:ADR-003-orientdb-cache.adoc[ADR-003]: OrientDB Embedded Database + +**Context:** Initial design focused on proving the concept of a Java-based static site generator. Primary concerns were simplicity and getting core features working. + +=== Phase 2: Maturity (2015-2017) - "Make it Extensible" + +* link:ADR-004-delegating-template-engine.adoc[ADR-004]: Delegating Template Engine Pattern +* link:ADR-005-configuration-abstraction.adoc[ADR-005]: Configuration Abstraction Layer +* link:ADR-006-incremental-builds.adoc[ADR-006]: SHA1-based Incremental Builds + +**Context:** User base growing, requests for multiple template engines and better performance. Focus shifted to extensibility without sacrificing simplicity. + +=== Phase 3: Modernization (2018-Present) - "Reduce Tech Debt" + +* link:ADR-007-deprecation-strategy.adoc[ADR-007]: Backwards Compatibility via Deprecation +* link:ADR-008-java-version-policy.adoc[ADR-008]: Java Version Support Policy + +**Context:** Balance between innovation and stability. Support legacy projects while modernizing codebase for future enhancements. + +== Decision Status Legend + +* ✅ **Accepted**: Active and implemented +* 🔄 **Superseded**: Replaced by newer decision (see "Superseded by" section) +* ❌ **Deprecated**: Being phased out +* 🤔 **Proposed**: Under discussion + +== Quick Reference by Topic + +=== Architecture & Design + +* Pipeline Architecture (ADR-001) +* Delegating Template Engine (ADR-004) + +=== Extensibility + +* Java SPI for Plugins (ADR-002) +* Configuration Abstraction (ADR-005) + +=== Performance + +* OrientDB Caching (ADR-003) +* Incremental Builds (ADR-006) + +=== Compatibility + +* Deprecation Strategy (ADR-007) +* Java Version Policy (ADR-008) + +== Template for New ADRs + +When adding new ADRs, use this format (see individual ADRs for examples): + +```adoc += ADR-XXX: [Title] + +Status: [Accepted|Proposed|Deprecated|Superseded] + +Date: YYYY-MM-DD + +== Problem Statement + +What problem are we solving? + +== Context + +Background, constraints, and forces at play. + +== Decision + +What we decided to do. + +== Consequences + +=== Positive + +* Benefits and advantages + +=== Negative + +* Drawbacks and limitations + +=== Neutral + +* Other impacts + +== Alternatives Considered + +=== Pugh Matrix + +| Criterion | Baseline | Alt 1 | Alt 2 | +|-----------------|----------|-------|-------| +| Criterion 1 | 0 | +1 | -1 | +| Total Score | 0 | X | Y | + +== Related Decisions + +* Links to related ADRs + +== References + +* External resources, discussions, issues +``` + +== Contributing ADRs + +When making significant architectural decisions: + +1. Create new ADR file with next number +2. Fill in all sections (Problem, Context, Decision, Consequences, Alternatives) +3. Include Pugh Matrix for alternatives evaluation +4. Update this README with timeline entry and topic links +5. Reference ADR in related code documentation diff --git a/docs/images/01_2_iso-25010-topics-EN.drawio.png b/docs/images/01_2_iso-25010-topics-EN.drawio.png new file mode 100644 index 000000000..548f6fa56 Binary files /dev/null and b/docs/images/01_2_iso-25010-topics-EN.drawio.png differ diff --git a/docs/images/05_building_blocks-EN.png b/docs/images/05_building_blocks-EN.png new file mode 100644 index 000000000..0862b64e3 Binary files /dev/null and b/docs/images/05_building_blocks-EN.png differ diff --git a/docs/images/08-concepts-EN.drawio.png b/docs/images/08-concepts-EN.drawio.png new file mode 100644 index 000000000..83f285b17 Binary files /dev/null and b/docs/images/08-concepts-EN.drawio.png differ diff --git a/docs/images/arc42-logo.png b/docs/images/arc42-logo.png new file mode 100644 index 000000000..88c76d063 Binary files /dev/null and b/docs/images/arc42-logo.png differ diff --git a/docs/llm/antipatterns.adoc b/docs/llm/antipatterns.adoc new file mode 100644 index 000000000..f1555b6cc --- /dev/null +++ b/docs/llm/antipatterns.adoc @@ -0,0 +1,703 @@ += JBake Anti-Patterns: What NOT to Do + +This document catalogs common mistakes, anti-patterns, and pitfalls when working with or extending JBake. + +== Architecture Anti-Patterns + +=== ❌ Antipattern: Exposing OrientDB Directly to Templates + +**Why wrong:** + +* Tight coupling between templates and database implementation +* OrientDB API changes break templates (v2 → v3 migration) +* Template authors must learn OrientDB query syntax +* Hard to migrate to different database + +**✅ Correct approach:** + +* Wrap queries in ContentStore methods +* Expose Java API: `getPublishedPosts()`, `getPostsByTag(tag)` +* Template uses simple calls: `${db.published_posts}` + +**Code example:** + +```freemarker +❌ Wrong: +<#-- Direct OrientDB query in template --> +<#assign posts = db.query("select * from Documents where status='published'")> + +✅ Correct: +<#-- Use ContentStore wrapper --> +<#assign posts = db.published_posts> +``` + +**Related:** ADR-003-orientdb-cache.adoc + +--- + +=== ❌ Antipattern: Mutable Global State + +**Why wrong:** + +* `DocumentTypes` and `ModelExtractors` use singletons with mutable state +* Thread-safety issues +* Unexpected behavior when running multiple bakes +* Hard to test (state leaks between tests) + +**✅ Correct approach:** + +* Pass configuration explicitly +* Use immutable objects where possible +* Reset global state carefully (see `resetDocumentTypesAndExtractors()`) + +**Code example:** + +```java +❌ Wrong: +// Mutating global singleton +DocumentTypes.addDocumentType("tutorial"); +// Another part of code affected + +✅ Correct: +// Configuration-based, explicit +config.setDocumentTypes(Arrays.asList("post", "page", "tutorial")); +// Or: Pass DocumentTypes instance to components +``` + +**Current status:** Technical debt; refactoring candidate for future versions. + +**Related:** Issues #123 (DocumentTypes refactoring) + +--- + +=== ❌ Antipattern: Breaking API Changes Without Deprecation + +**Why wrong:** + +* Breaks existing projects +* No migration path for users +* Violates semantic versioning + +**✅ Correct approach:** + +* Deprecate old API first +* Provide new API alongside +* Document migration in release notes +* Remove deprecated API only in major version + +**Code example:** + +```java +❌ Wrong: +// Remove old constructor immediately +public class Oven { + // Old constructor removed - breaks code + public Oven(JBakeConfiguration config) { /* ... */ } +} + +✅ Correct: +// Deprecate, then remove +public class Oven { + @Deprecated + public Oven(File source, File dest, boolean clearCache) { + this(new JBakeConfigurationFactory().create(...)); + } + + public Oven(JBakeConfiguration config) { /* ... */ } +} +``` + +**Related:** ADR-007-deprecation-strategy.adoc + +--- + +== Configuration Anti-Patterns + +=== ❌ Antipattern: Hardcoding Configuration Values + +**Why wrong:** + +* Not user-configurable +* Breaks customization +* Forces code changes for simple tweaks + +**✅ Correct approach:** + +* Define in `default.properties` with sensible default +* Allow override in user's `jbake.properties` +* Access via `JBakeConfiguration` interface + +**Code example:** + +```java +❌ Wrong: +// Hardcoded value +String dateFormat = "yyyy-MM-dd"; + +✅ Correct: +// From configuration +String dateFormat = config.getDateFormat(); + +// In default.properties: +// date.format=yyyy-MM-dd +``` + +--- + +=== ❌ Antipattern: Using CompositeConfiguration Directly + +**Why wrong:** + +* Deprecated pattern +* Tight coupling to Commons Configuration library +* Hard to change configuration source +* No type safety + +**✅ Correct approach:** + +* Use `JBakeConfiguration` interface +* Access via typed methods +* Configuration abstraction enables future changes + +**Code example:** + +```java +❌ Wrong: +CompositeConfiguration config = ...; +String value = config.getString("some.property"); + +✅ Correct: +JBakeConfiguration config = ...; +String value = config.getSomeProperty(); +``` + +**Related:** ADR-005-configuration-abstraction.adoc + +--- + +== Template Development Anti-Patterns + +=== ❌ Antipattern: Assuming Template File Extension = Engine + +**Why wrong:** + +* `.html` maps to RawMarkupEngine (passthrough), not Thymeleaf +* Confusion when template not processed +* Silent failures + +**✅ Correct approach:** + +* Use engine-specific extensions: + - Freemarker: `.ftl`, `.ftlh` + - Groovy: `.groovy`, `.gsp` + - Thymeleaf: `.thymeleaf` + - Pebble: `.pebble` +* Or configure extension mapping explicitly + +**Code example:** + +``` +❌ Wrong: +templates/ + post.html → Treated as raw HTML, not processed! + +✅ Correct: +templates/ + post.ftl → Freemarker + post.groovy → Groovy + post.thymeleaf → Thymeleaf +``` + +**Related:** `TemplateEngines.properties` mapping + +--- + +=== ❌ Antipattern: Mutating Content in Templates + +**Why wrong:** + +* DocumentModel is cached, mutations affect cache +* Side effects hard to track +* Breaks functional programming principles + +**✅ Correct approach:** + +* Treat content as immutable in templates +* Create new variables for transformations +* Use template helpers for complex logic + +**Code example:** + +```freemarker +❌ Wrong: +<#-- Mutating content --> +<#assign content.processedTitle = content.title?upper_case> + +✅ Correct: +<#-- Create new variable --> +<#assign processedTitle = content.title?upper_case> +

${processedTitle}

+``` + +--- + +=== ❌ Antipattern: Complex Logic in Templates + +**Why wrong:** + +* Templates become hard to read +* Logic duplicated across templates +* Testing difficult + +**✅ Correct approach:** + +* Extract complex logic to Java code +* Use ModelExtractors for data transformation +* Create template helpers +* Keep templates focused on presentation + +**Code example:** + +```freemarker +❌ Wrong: +<#-- Complex date formatting logic in template --> +<#assign year = content.date?string("yyyy")> +<#assign month = content.date?string("MM")> +<#assign day = content.date?string("dd")> +<#assign formatted = "${year}-${month}-${day}"> + +✅ Correct: +<#-- Use pre-formatted date from model --> +${content.formattedDate} + +// In Java (ModelExtractor or custom field): +doc.put("formattedDate", dateFormat.format(doc.getDate())); +``` + +--- + +== Parser Development Anti-Patterns + +=== ❌ Antipattern: Returning null on Parse Errors + +**Why wrong:** + +* Silent failures +* Content mysteriously missing from output +* Hard to debug + +**Current behavior:** Parser returns null on failure (logged but not thrown) + +**✅ Better approach (future):** + +* Throw `ParsingException` with details +* Oven collects errors for summary +* User gets clear error message + +**Code example:** + +```java +❌ Current (anti-pattern): +public DocumentModel processFile(File file) { + try { + // ... parsing + } catch (Exception e) { + LOGGER.error("Failed to parse", e); + return null; // Silent failure! + } +} + +✅ Better: +public DocumentModel processFile(File file) throws ParsingException { + try { + // ... parsing + } catch (Exception e) { + throw new ParsingException(file, e); // Explicit failure + } +} +``` + +**Status:** Technical debt; discussed in Issue #234 + +--- + +=== ❌ Antipattern: Overriding parse() Instead of Hooks + +**Why wrong:** + +* `parse()` is template method, controls flow +* Overriding breaks header/body separation +* Duplicates base class logic + +**✅ Correct approach:** + +* Override `processHeader()` and `processBody()` hooks +* Let base class handle flow control + +**Code example:** + +```java +❌ Wrong: +public class MyEngine extends MarkupEngine { + @Override + public DocumentModel parse(JBakeConfiguration config, File file) { + // Reimplements entire flow - bad! + // ... duplicate logic + } +} + +✅ Correct: +public class MyEngine extends MarkupEngine { + @Override + protected void processHeader(ParserContext context) { + // Just handle header extraction + } + + @Override + protected void processBody(ParserContext context) { + // Just handle body conversion + } +} +``` + +**Related:** `MarkupEngine.java` template method pattern + +--- + +== Content Authoring Anti-Patterns + +=== ❌ Antipattern: Omitting Required Metadata + +**Why wrong:** + +* Content won't render (missing type or status) +* Default status is "draft" (not rendered) +* Confusing "where's my content?" issues + +**✅ Correct approach:** + +* Always specify `type` and `status` in header +* Or configure defaults in `jbake.properties` + +**Code example:** + +```markdown +❌ Wrong: +title=My Post +date=2025-11-22 +~~~~~~ +Content here... +# Missing type and status! + +✅ Correct: +title=My Post +date=2025-11-22 +type=post +status=published +~~~~~~ +Content here... + +# Or configure defaults: +# default.type=post +# default.status=published +``` + +--- + +=== ❌ Antipattern: Inconsistent Date Formats + +**Why wrong:** + +* Parsing errors +* Dates not sortable +* Archive generation fails + +**✅ Correct approach:** + +* Use consistent format matching `date.format` config +* Default: `yyyy-MM-dd` +* Stick to ISO 8601 formats + +**Code example:** + +```markdown +❌ Wrong: +date=November 22, 2025 # Doesn't match default format! +date=22/11/2025 # Ambiguous! + +✅ Correct: +date=2025-11-22 # ISO 8601, matches default + +# Or configure format: +# date.format=dd-MM-yyyy +``` + +--- + +== Extension Development Anti-Patterns + +=== ❌ Antipattern: Missing META-INF/services File + +**Why wrong:** + +* ServiceLoader can't discover plugin +* Silent failure (plugin not loaded) +* No error message + +**✅ Correct approach:** + +* Always create META-INF/services file +* Use fully-qualified class names +* Test plugin loading + +**Code example:** + +``` +❌ Wrong: +src/ + main/ + java/ + com/example/MyTool.java # Implements RenderingTool + # Missing META-INF/services! + +✅ Correct: +src/ + main/ + java/ + com/example/MyTool.java + resources/ + META-INF/ + services/ + org.jbake.render.RenderingTool # Contains: com.example.MyTool +``` + +--- + +=== ❌ Antipattern: Assuming ServiceLoader Order + +**Why wrong:** + +* ServiceLoader iteration order is undefined +* Behavior differs across JVMs +* Tests pass locally, fail in CI + +**✅ Correct approach:** + +* Don't depend on load order +* Use explicit priority mechanism if order matters +* Make tools independent + +**Code example:** + +```java +❌ Wrong: +// Assuming Tool A runs before Tool B +for (RenderingTool tool : ServiceLoader.load(RenderingTool.class)) { + tool.render(...); // Order matters but not guaranteed! +} + +✅ Correct: +// Tools are independent, order doesn't matter +// Or: Implement Priority interface and sort explicitly +List tools = ...; +tools.sort(Comparator.comparing(RenderingTool::getPriority)); +``` + +**Status:** Future enhancement; see Issue #345 + +--- + +== Performance Anti-Patterns + +=== ❌ Antipattern: Disabling Cache for Every Bake + +**Why wrong:** + +* Defeats incremental build purpose +* Slow rebuilds on large sites +* Unnecessary re-parsing + +**✅ Correct approach:** + +* Use `-clearCache` only when needed: + - After template changes + - After config changes + - After JBake upgrade +* Normal bakes use cache automatically + +**Code example:** + +```bash +❌ Wrong: +# Always clearing cache +jbake -b -clearCache # Slow! + +✅ Correct: +# Normal incremental bake +jbake -b # Fast, uses cache + +# Clear only when needed +jbake -b -clearCache # After template/config change +``` + +--- + +=== ❌ Antipattern: Loading All Content into Memory at Once + +**Why wrong:** + +* Out of memory on large sites +* Slow startup +* Thymeleaf limitation (loads full model) + +**✅ Correct approach:** + +* Use lazy loading (ModelExtractors) +* Query ContentStore on-demand +* Prefer Freemarker/Groovy over Thymeleaf for large sites + +**Code example:** + +```java +❌ Wrong: +// Load everything upfront +List allPosts = db.getAllContent("post"); +model.put("posts", allPosts); // Entire list in memory! + +✅ Correct: +// Lazy load via ModelExtractor +model.put("posts", () -> db.getPublishedPosts()); // Evaluated when accessed +``` + +**Note:** See Thymeleaf warning in `ThymeleafTemplateEngine.java` + +--- + +== Testing Anti-Patterns + +=== ❌ Antipattern: Not Using Test Fixtures + +**Why wrong:** + +* Tests create one-off structures +* Inconsistent test data +* Hard to reproduce issues + +**✅ Correct approach:** + +* Use canonical fixtures: `src/test/resources/fixture/` +* Extend `ContentStoreIntegrationTest` for DB tests +* Reuse `TestUtils.getTestResourcesAsSourceFolder()` + +**Code example:** + +```java +❌ Wrong: +@Test +public void testParser() { + File file = createTempFile(); // One-off structure + // ... +} + +✅ Correct: +@Test +public void testParser() { + File fixture = TestUtils.getTestResourcesAsSourceFolder(); + File file = new File(fixture, "content/blog/2013/first-post.md"); + // Uses canonical fixture structure +} +``` + +--- + +=== ❌ Antipattern: State Leakage Between Tests + +**Why wrong:** + +* Tests pass individually, fail in suite +* Order-dependent tests +* Flaky CI builds + +**✅ Correct approach:** + +* Reset global state in `@Before` or `@After` +* Use `@Rule TemporaryFolder` for file tests +* Call `db.drop()` in teardown + +**Code example:** + +```java +❌ Wrong: +public class MyTest { + @Test + public void test1() { + DocumentTypes.addDocumentType("tutorial"); # Global mutation! + } + + @Test + public void test2() { + // Affected by test1! + } +} + +✅ Correct: +public class MyTest extends ContentStoreIntegrationTest { + @Before + public void setUp() { + db.startup(); + DocumentTypes.resetDocumentTypes(); // Clean state + } + + @After + public void tearDown() { + db.drop(); // Clean DB + } +} +``` + +--- + +## Summary: Key Principles + +**DO:** + +* ✅ Use abstraction layers (JBakeConfiguration, ContentStore) +* ✅ Deprecate before removing +* ✅ Wrap third-party APIs +* ✅ Keep templates simple +* ✅ Follow SPI patterns correctly +* ✅ Use configuration for customization +* ✅ Write tests with fixtures + +**DON'T:** + +* ❌ Expose implementation details to users +* ❌ Break APIs without deprecation +* ❌ Hardcode values +* ❌ Mutate shared state +* ❌ Put logic in templates +* ❌ Assume ServiceLoader order +* ❌ Skip META-INF/services + +--- + +## Quick Reference: "I want to..." → "Do this, not that" + +| Goal | ❌ Anti-Pattern | ✅ Correct Approach | +|------|----------------|-------------------| +| Add custom field | Modify DocumentModel class | Add field in content header | +| Query content | OrientDB SQL in template | ContentStore methods | +| Add template engine | Modify TemplateEngines | Implement SPI + META-INF | +| Change config | Hardcode value | Add to properties file | +| Handle parse error | Return null | Throw exception (future) | +| Support new format | Add to Parser | Extend MarkupEngine | +| Optimize performance | Always clearCache | Use incremental builds | +| Test components | Create temp structure | Use fixtures | + +--- + +**See also:** + +* `docs/arc42/decisions/` - Architecture Decision Records +* `docs/arc42/chapters/08_concepts.adoc` - Mental Model Concepts +* `docs/onboarding/common-issues.adoc` - Troubleshooting Guide diff --git a/docs/llm/knowledge-graph.adoc b/docs/llm/knowledge-graph.adoc new file mode 100644 index 000000000..8a96230f8 --- /dev/null +++ b/docs/llm/knowledge-graph.adoc @@ -0,0 +1,471 @@ += JBake Knowledge Graph for LLM Context + +This structured knowledge graph is optimized for LLM understanding and code generation tasks. + +== Core Concepts Hierarchy + +=== Level 0: Fundamental Concepts (Learn First) + +==== DocumentModel + +```yaml +name: DocumentModel +level: 0 +priority: CRITICAL +description: Map-based structure representing parsed content with metadata +prerequisites: [] +enables: + - Template rendering + - ContentStore queries + - Custom field support +learning_time: 1-2 hours +key_files: + - jbake-core/src/main/java/org/jbake/model/DocumentModel.java + - jbake-core/src/main/java/org/jbake/model/ModelAttributes.java +common_mistakes: + - mistake: "Treating DocumentModel as strongly-typed POJO" + why: "It's a Map, keys are strings" + correct: "Use get(key) and type-check values, or use typed getters" + - mistake: "Mutating DocumentModel after parsing" + why: "Mutations affect cached version in OrientDB" + correct: "Create new DocumentModel or use immutable patterns" + - mistake: "Missing required fields (type, status, sourceUri)" + why: "ContentStore expects these for indexing" + correct: "Always set via setType(), setStatus(), setSourceUri()" +validation_question: "How does a custom field 'author: John' in Markdown front-matter become accessible in a Freemarker template?" +correct_answer: "Parser extracts header, puts 'author'='John' in DocumentModel map, template accesses as ${content.author}" +code_example: | + // Creating DocumentModel + DocumentModel doc = new DocumentModel(); + doc.setTitle("My Post"); + doc.setType("post"); + doc.setStatus("published"); + doc.setDate(new Date()); + doc.setBody("

HTML content

"); + doc.put("customField", "customValue"); // Custom metadata + + // In Freemarker template +

${content.title}

+

${content.customField}

+``` + +==== Pipeline Architecture + +```yaml +name: Pipeline Architecture +level: 0 +priority: CRITICAL +description: Linear processing stages - Crawl → Parse → Render → Copy +prerequisites: [] +enables: + - Understanding code flow + - Debugging + - Extending JBake +learning_time: 2-3 hours +key_files: + - jbake-core/src/main/java/org/jbake/app/Oven.java +common_mistakes: + - mistake: "Assuming stages run in parallel" + why: "Sequential by design for simplicity" + correct: "Stages execute one after another" + - mistake: "Modifying files during bake" + why: "Crawler has already scanned; changes not seen" + correct: "Re-run bake after file modifications" +validation_question: "If you modify a template file during a bake, will it affect the current bake?" +correct_answer: "No, templates are loaded at Renderer stage start; mid-bake changes ignored" +code_example: | + // Oven.bake() orchestrates pipeline + public void bake() { + contentStore.startup(); // 1. Initialize DB + updateDocTypesFromConfiguration(); + crawler.crawl(); // 2. Find & parse files + crawler.crawlDataFiles(); + renderContent(); // 3. Apply templates + asset.copy(); // 4. Copy static files + contentStore.close(); + } +``` + +==== Configuration System + +```yaml +name: JBakeConfiguration +level: 0 +priority: HIGH +description: Abstraction over Commons Configuration2, provides all settings +prerequisites: [] +enables: + - Customizing behavior + - Creating projects + - Testing with mock configs +learning_time: 1 hour +key_files: + - jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfiguration.java + - jbake-core/src/main/java/org/jbake/app/configuration/DefaultJBakeConfiguration.java +common_mistakes: + - mistake: "Using CompositeConfiguration directly in new code" + why: "Deprecated pattern, tight coupling" + correct: "Use JBakeConfiguration interface methods" + - mistake: "Assuming mutable configuration" + why: "Configuration should be immutable after creation" + correct: "Create new configuration for changes, don't mutate existing" +validation_question: "Where does JBake look for configuration and in what order?" +correct_answer: "1. default.properties (built-in), 2. jbake.properties (project), 3. programmatic overrides" +code_example: | + // Access configuration + JBakeConfiguration config = // ... from factory + File contentFolder = config.getContentFolder(); + String dateFormat = config.getDateFormat(); + List docTypes = config.getDocumentTypes(); + + // Template file for document type + String postTemplate = config.getTemplateByDocType("post"); // "post.ftl" +``` + +=== Level 1: Architecture Concepts + +==== ContentStore (OrientDB Cache) + +```yaml +name: ContentStore +level: 1 +priority: HIGH +description: Wrapper around embedded OrientDB for caching and querying content +prerequisites: + - DocumentModel + - Pipeline Architecture +enables: + - Incremental builds + - Tag queries + - Archive generation + - Feed creation +learning_time: 3-4 hours +key_files: + - jbake-core/src/main/java/org/jbake/app/ContentStore.java + - jbake-core/src/main/java/org/jbake/app/DBUtil.java +common_mistakes: + - mistake: "Exposing OrientDB APIs directly" + why: "Creates tight coupling, migration difficult" + correct: "Wrap in ContentStore methods" + - mistake: "Forgetting to call startup() and close()" + why: "OrientDB needs lifecycle management" + correct: "Always startup before use, close in finally block" + - mistake: "Using SQL strings directly" + why: "SQL injection risk, hard to maintain" + correct: "Use prepared statement constants" +validation_question: "How does ContentStore enable incremental builds?" +correct_answer: "Stores SHA1 hash of each file; on next bake, compares SHA1 to detect changes" +code_example: | + // Querying ContentStore + ContentStore db = // ... created by UtensilsFactory + db.startup(); + + try { + // Get published posts + DocumentList posts = db.getPublishedPosts(); + + // Get posts by tag + DocumentList javaPosts = db.getPostsByTag("java"); + + // Get all tags + Set tags = db.getAllTags(); + + // Check if content changed + DocumentList status = db.getDocumentStatus("/path/to/file"); + + } finally { + db.close(); + db.shutdown(); + } +``` + +==== Java SPI Extensibility + +```yaml +name: ServiceLoader Plugin Pattern +level: 1 +priority: HIGH +description: Java SPI mechanism for discovering template engines, markup parsers, and rendering tools +prerequisites: + - Pipeline Architecture +enables: + - Adding new template engines + - Custom markup parsers + - Custom renderers (feeds, sitemaps) +learning_time: 2-3 hours +key_files: + - jbake-core/src/main/java/org/jbake/template/TemplateEngines.java + - jbake-core/src/main/java/org/jbake/parser/Engines.java + - jbake-core/src/main/resources/META-INF/services/org.jbake.render.RenderingTool +common_mistakes: + - mistake: "Forgetting META-INF/services file" + why: "ServiceLoader needs it for discovery" + correct: "Create META-INF/services/ with implementing class names" + - mistake: "Typo in service file" + why: "Class not found exception, hard to debug" + correct: "Use fully-qualified class names, verify spelling" + - mistake: "Assuming load order" + why: "ServiceLoader order is non-deterministic" + correct: "Don't depend on order; use explicit priority if needed" +validation_question: "What three steps are required to add a new RenderingTool?" +correct_answer: "1. Implement RenderingTool interface, 2. Create META-INF/services/org.jbake.render.RenderingTool, 3. Add class name to service file" +code_example: | + // Implementing a custom RenderingTool + package com.example.jbake; + + import org.jbake.render.RenderingTool; + import org.jbake.app.Renderer; + import org.jbake.app.ContentStore; + import org.jbake.app.configuration.JBakeConfiguration; + + public class CustomRenderer implements RenderingTool { + @Override + public int render(Renderer renderer, ContentStore db, JBakeConfiguration config) { + // Query content + DocumentList docs = db.getAllContent("custom-type"); + + // Render each document + int count = 0; + for (DocumentModel doc : docs) { + renderer.render(doc); + count++; + } + return count; + } + } + + // META-INF/services/org.jbake.render.RenderingTool + com.example.jbake.CustomRenderer +``` + +==== Template Engine Delegation + +```yaml +name: DelegatingTemplateEngine +level: 1 +priority: MEDIUM +description: Routes rendering to specific engines based on file extension +prerequisites: + - Java SPI Extensibility +enables: + - Multiple template engines in one project + - Template fallback + - Custom template engines +learning_time: 2 hours +key_files: + - jbake-core/src/main/java/org/jbake/template/DelegatingTemplateEngine.java + - jbake-core/src/main/java/org/jbake/template/AbstractTemplateEngine.java +common_mistakes: + - mistake: "Creating template with .html extension expecting Thymeleaf" + why: ".html maps to RawMarkupEngine (passthrough)" + correct: "Use engine-specific extension or configure mapping" + - mistake: "Not registering engine extensions" + why: "DelegatingTemplateEngine won't route to your engine" + correct: "Add extension mapping in META-INF/org.jbake.parser.TemplateEngines.properties" +validation_question: "How does DelegatingTemplateEngine choose which engine to use?" +correct_answer: "Looks up file extension in TemplateEngines registry, delegates to matching engine" +code_example: | + // Extension → Engine mapping + // META-INF/org.jbake.parser.TemplateEngines.properties + org.jbake.template.FreemarkerTemplateEngine=ftl,ftlh + org.jbake.template.GroovyTemplateEngine=groovy,gsp + org.jbake.template.ThymeleafTemplateEngine=thymeleaf + + // DelegatingTemplateEngine routing + String ext = FileUtil.fileExt("post.ftl"); // "ftl" + AbstractTemplateEngine engine = renderers.getEngine(ext); // FreemarkerTemplateEngine + engine.renderDocument(model, "post.ftl", writer); +``` + +=== Level 2: Implementation Details + +==== Markup Parsing Pipeline + +```yaml +name: MarkupEngine Template Method +level: 2 +priority: MEDIUM +description: Base class for parsers with template method pattern +prerequisites: + - DocumentModel + - Pipeline Architecture +enables: + - Custom markup parsers + - Understanding parsing flow +learning_time: 2-3 hours +key_files: + - jbake-core/src/main/java/org/jbake/parser/MarkupEngine.java + - jbake-core/src/main/java/org/jbake/parser/MarkdownEngine.java + - jbake-core/src/main/java/org/jbake/parser/AsciidoctorEngine.java +common_mistakes: + - mistake: "Overriding parse() instead of processHeader()/processBody()" + why: "parse() is template method, should not be overridden" + correct: "Override processHeader() and processBody()" + - mistake: "Not handling header separator" + why: "MarkupEngine base class expects header/body split" + correct: "Use header.separator config (default ~~~~~~) or skip header for headerless formats" +validation_question: "What's the parsing flow for a Markdown file?" +correct_answer: "1. MarkupEngine.parse() calls processHeader() → extracts metadata, 2. calls processBody() → Markdown to HTML, 3. returns DocumentModel" +code_example: | + // MarkupEngine template method + public DocumentModel parse(JBakeConfiguration config, File file) { + // ... read file, detect header + processHeader(context); // Extract metadata to DocumentModel + processBody(context); // Convert markup to HTML + return context.getDocumentModel(); + } + + // MarkdownEngine implementation + @Override + public void processBody(ParserContext context) { + Parser parser = Parser.builder(options).build(); + HtmlRenderer renderer = HtmlRenderer.builder(options).build(); + Document document = parser.parse(context.getBody()); + context.setBody(renderer.render(document)); // Markdown → HTML + } +``` + +==== SHA1 Change Detection + +```yaml +name: Incremental Build Mechanism +level: 2 +priority: MEDIUM +description: Hash-based change detection to avoid re-parsing unchanged files +prerequisites: + - ContentStore + - Pipeline Architecture +enables: + - Fast rebuilds + - Large site performance +learning_time: 1-2 hours +key_files: + - jbake-core/src/main/java/org/jbake/app/Crawler.java (buildHash, findDocumentStatus) +common_mistakes: + - mistake: "Expecting template changes to trigger content re-parse" + why: "Templates have separate signature mechanism" + correct: "Template changes re-render without re-parsing; use -clearCache to force full rebuild" + - mistake: "Assuming binary asset SHA1 checking" + why: "Assets copied by file modification time, not SHA1" + correct: "Assets always copied; use asset.ignore for exclusions" +validation_question: "What triggers content re-parsing?" +correct_answer: "SHA1 hash change of file content, or -clearCache flag" +code_example: | + // Crawler.crawlFile() change detection + String sha1 = buildHash(sourceFile); // Compute SHA1 of file + String uri = buildURI(sourceFile); + DocumentStatus status = findDocumentStatus(uri, sha1); + + if (status == DocumentStatus.UPDATED) { + db.deleteContent(uri); // Remove old version + processSourceFile(sourceFile, sha1, uri); // Re-parse + } else if (status == DocumentStatus.IDENTICAL) { + // Skip, use cached version + } else if (status == DocumentStatus.NEW) { + processSourceFile(sourceFile, sha1, uri); + } +``` + +== Cross-Reference Matrix + +**When working on...** + +| Task | Key Concepts | Files to Read | +|------|--------------|---------------| +| Adding custom document type | DocumentModel, Configuration, Renderer | DocumentTypes.java, Renderer.java, DefaultJBakeConfiguration.java | +| Creating template engine | SPI, DelegatingTemplateEngine, AbstractTemplateEngine | AbstractTemplateEngine.java, TemplateEngines.java, META-INF/services | +| Implementing markup parser | MarkupEngine, Parser, Engines | MarkupEngine.java, Engines.java, existing engine implementations | +| Optimizing performance | ContentStore, SHA1, Pipeline | ContentStore.java, Crawler.java, Oven.java | +| Debugging rendering | DelegatingTemplateEngine, Renderer, RenderingTool | DelegatingTemplateEngine.java, Renderer.java, specific tool | +| Writing tests | ContentStoreIntegrationTest, fixtures | test/resources/fixture/, ContentStoreIntegrationTest.java | + +== Code Patterns + +=== Pattern: Factory Creation (Utensils) + +**Use when:** Creating wired components with shared configuration + +```java +// UtensilsFactory creates all components +Utensils utensils = UtensilsFactory.createDefaultUtensils(config); +ContentStore db = utensils.getContentStore(); +Crawler crawler = utensils.getCrawler(); +Renderer renderer = utensils.getRenderer(); +// All share same JBakeConfiguration instance +``` + +=== Pattern: Service Provider Interface + +**Use when:** Extending JBake with plugins + +```java +// 1. Implement interface +public class MyTool implements RenderingTool { + public int render(Renderer r, ContentStore db, JBakeConfiguration config) { + // Implementation + } +} + +// 2. Register in META-INF/services/org.jbake.render.RenderingTool +com.example.MyTool + +// 3. JBake discovers via ServiceLoader +for (RenderingTool tool : ServiceLoader.load(RenderingTool.class)) { + tool.render(renderer, contentStore, config); +} +``` + +=== Pattern: Template Method (MarkupEngine) + +**Use when:** Creating parsers with common structure + +```java +public abstract class MarkupEngine implements ParserEngine { + // Template method + public final DocumentModel parse(JBakeConfiguration config, File file) { + ParserContext context = new ParserContext(); + // ... setup + processHeader(context); // Hook 1 + processBody(context); // Hook 2 + return context.getDocumentModel(); + } + + // Hooks for subclasses + protected abstract void processHeader(ParserContext context); + protected abstract void processBody(ParserContext context); +} +``` + +=== Pattern: Query Object (DocumentList) + +**Use when:** Working with content from database + +```java +// ContentStore returns DocumentList (extends ArrayList) +DocumentList posts = db.getPublishedPosts(); +for (DocumentModel post : posts) { + String title = post.getTitle(); + Date date = post.getDate(); + String body = post.getBody(); +} + +// Filter and transform +DocumentList javaPosts = db.getPostsByTag("java"); +``` + +== Anti-Patterns to Avoid + +See: `docs/llm/antipatterns.adoc` + +== Glossary + +* **Bake**: The process of generating a static site from content and templates +* **Document Type**: Category of content (post, page, custom) that determines template used +* **Status**: Published state (draft, published) that determines if content is rendered +* **Markup Engine**: Parser that converts markup format (Markdown, AsciiDoc) to HTML +* **Template Engine**: Renderer that applies templates (Freemarker, Groovy) to content +* **ContentStore**: OrientDB wrapper providing caching and query capabilities +* **RenderingTool**: SPI plugin for custom renderers (feeds, sitemaps, archives) +* **Utensils**: Factory-created components used by Oven (Crawler, Parser, Renderer, etc.) +* **Pipeline**: Sequential processing stages (Crawl, Parse, Render, Copy) +* **SHA1**: Hash used for change detection to enable incremental builds diff --git a/docs/onboarding/journey-map.adoc b/docs/onboarding/journey-map.adoc new file mode 100644 index 000000000..fee8cd9bb --- /dev/null +++ b/docs/onboarding/journey-map.adoc @@ -0,0 +1,574 @@ += JBake Developer Onboarding - 4-Week Journey + +This guide provides a structured learning path for new senior developers joining the JBake project. Each week builds on the previous, moving from overview to independence. + +== Week 1: Overview & Orientation + +**Goal**: Understand what JBake is, why it exists, and how it works at a high level. + +=== Learning Objectives + +* [ ] Explain JBake's purpose and target users +* [ ] Describe the "bakery" metaphor and how it maps to components +* [ ] Run JBake locally and generate a site +* [ ] Identify the four pipeline stages (Crawl, Parse, Render, Copy) +* [ ] Navigate the codebase structure + +=== Activities + +==== Day 1-2: Setup & First Bake + +1. **Environment Setup** + * Clone repository: `git clone https://github.com/jbake-org/jbake.git` + * Build project: `./gradlew build` + * Run tests: `./gradlew test` + * Create example site: `./gradlew run --args="-i example"` + +2. **Run Your First Bake** + * Initialize example project with: `java -jar build/libs/jbake-*.jar -i -b example` + * Examine the generated output in `example/output/` + * Modify `example/content/blog/2013/first-post.md` + * Re-bake and see changes: `java -jar build/libs/jbake-*.jar -b example` + +3. **Explore Example Project Structure** + ``` + example/ + ├── assets/ # Static files (CSS, JS, images) + ├── content/ # Markup files (Markdown, AsciiDoc) + ├── templates/ # Template files (Freemarker, Groovy, etc.) + ├── jbake.properties # Configuration + └── output/ # Generated HTML (created after baking) + ``` + +==== Day 3: Code Walkthrough + +Read these files in order to understand the flow: + +1. `jbake-core/src/main/java/org/jbake/launcher/Main.java` - Entry point +2. `jbake-core/src/main/java/org/jbake/launcher/Baker.java` - Bake orchestration +3. `jbake-core/src/main/java/org/jbake/app/Oven.java` - Core pipeline +4. `jbake-core/src/main/java/org/jbake/app/Crawler.java` - File discovery +5. `jbake-core/src/main/java/org/jbake/app/Parser.java` - Content parsing + +==== Day 4-5: Documentation Deep Dive + +* Read: `README.asciidoc` - Project overview +* Read: `docs/arc42/chapters/01_introduction_and_goals.adoc` - System vision +* Read: `docs/arc42/chapters/04_solution_strategy.adoc` - Key decisions +* Read: `docs/arc42/chapters/08_concepts.adoc` - Mental model concepts +* Watch: Debug `Oven.bake()` method in IDE, step through pipeline + +=== Validation Questions + +Answer these to confirm understanding: + +1. **What are the four stages of the JBake pipeline?** + * Expected: Crawl, Parse, Render, Copy (Asset) + +2. **What's the difference between `content/` and `assets/` folders?** + * Expected: `content/` has markup files (processed), `assets/` has static files (copied as-is) + +3. **What happens when you run `jbake -b`?** + * Expected: Full bake - crawls all content, parses changed files, renders templates, copies assets + +4. **Where is configuration stored and what format?** + * Expected: `jbake.properties` file using Java Properties format + +5. **Name three supported markup formats.** + * Expected: Markdown, AsciiDoc, HTML (also JSON/YAML for data) + +=== Resources + +* User Documentation: http://jbake.org/docs/ +* GitHub Repository: https://github.com/jbake-org/jbake +* Gitter Chat: https://gitter.im/jbake-org/jbake +* Test Fixtures: `jbake-core/src/test/resources/fixture/` - canonical structure examples + +=== Week 1 Checkpoint + +**Self-Assessment Checklist:** + +* [ ] I can build JBake from source +* [ ] I can generate a site and understand the output +* [ ] I can identify which code handles crawling, parsing, and rendering +* [ ] I understand the bakery metaphor and can explain it to someone else +* [ ] I've read the key arc42 sections + +**Mentor Checkpoint:** + +Schedule 30min with mentor to: + +* Walk through your understanding of the pipeline +* Discuss any confusing parts +* Get guidance on Week 2 focus areas + +--- + +== Week 2: Fundamentals - Content Processing + +**Goal**: Understand how content flows from markup files to HTML output. + +=== Learning Objectives + +* [ ] Explain DocumentModel structure and lifecycle +* [ ] Trace a file from disk to DocumentModel to HTML +* [ ] Understand SHA1-based change detection +* [ ] Differentiate between markup engines and template engines +* [ ] Comprehend status (draft/published) and type (post/page) semantics + +=== Activities + +==== Day 1-2: Document Model Deep Dive + +1. **Read Core Classes** + * `jbake-core/src/main/java/org/jbake/model/DocumentModel.java` + * `jbake-core/src/main/java/org/jbake/model/ModelAttributes.java` + * `jbake-core/src/main/java/org/jbake/model/DocumentTypes.java` + +2. **Experiment with Document Fields** + * Create a test Markdown file with custom fields: + ```markdown + title=Test Document + date=2025-11-22 + type=post + status=published + tags=test,learning + myCustomField=Hello World + ~~~~~~ + # Content + + This is test content. + ``` + * Add debug breakpoint in `Parser.processFile()` + * Observe DocumentModel population + * Check how `myCustomField` appears in model + +3. **Study Parsers** + * Read: `jbake-core/src/main/java/org/jbake/parser/MarkupEngine.java` - Base class + * Read: `jbake-core/src/main/java/org/jbake/parser/MarkdownEngine.java` - Markdown impl + * Read: `jbake-core/src/main/java/org/jbake/parser/AsciidoctorEngine.java` - AsciiDoc impl + * Read: `jbake-core/src/main/java/org/jbake/parser/Engines.java` - Engine registry + +==== Day 3: SHA1 Change Detection + +1. **Understand the Mechanism** + * Read: `Crawler.crawlFile()` method - see SHA1 computation + * Read: `ContentStore.getDocumentStatus()` - see hash comparison + * Study DocumentStatus enum: NEW, IDENTICAL, UPDATED + +2. **Experiment** + * Bake a site once + * Check `.jbake` cache directory (if using plocal DB) + * Modify one file + * Bake again with logging: `./gradlew run --args="-b example" --info` + * Observe which files get re-parsed + +==== Day 4-5: Template Engines + +1. **Study Template Engine Architecture** + * Read: `jbake-core/src/main/java/org/jbake/template/AbstractTemplateEngine.java` + * Read: `jbake-core/src/main/java/org/jbake/template/DelegatingTemplateEngine.java` + * Read: `jbake-core/src/main/java/org/jbake/template/FreemarkerTemplateEngine.java` + * Read: `jbake-core/src/main/java/org/jbake/template/TemplateEngines.java` + +2. **Create a Simple Template** + * Add `test.ftl` to `example/templates/`: + ```freemarker + + + ${content.title} + +

${content.title}

+

Type: ${content.type}

+

Status: ${content.status}

+
${content.body}
+ + + ``` + * Configure in `jbake.properties`: `template.mytype.file=test.ftl` + * Create content file with `type=mytype` + * Bake and examine output + +=== Validation Questions + +1. **What is a DocumentModel and what does it contain?** + * Expected: Map-based structure with metadata (title, date, tags, status, type) and body (HTML) + +2. **How does JBake know which parser to use for a file?** + * Expected: File extension determines parser via Engines registry (.md → Markdown, .adoc → AsciiDoc) + +3. **Explain the three DocumentStatus values.** + * Expected: NEW (first time), IDENTICAL (unchanged SHA1), UPDATED (changed SHA1) + +4. **What's the difference between a markup engine and a template engine?** + * Expected: Markup engines parse content (Markdown → HTML); template engines apply presentation (Freemarker, Groovy) + +5. **What happens if you set `status=draft`?** + * Expected: Content parsed and cached but not rendered to output (not published) + +=== Hands-On Exercise + +**Create a Custom Document Type:** + +1. Define new type: `tutorial` +2. Create config: `template.tutorial.file=tutorial.ftl` +3. Create template: `templates/tutorial.ftl` +4. Create content: `content/tutorials/my-tutorial.adoc` with `type=tutorial` +5. Bake and verify output +6. Submit PR-style branch for mentor review (don't merge) + +=== Week 2 Checkpoint + +**Self-Assessment:** + +* [ ] I can trace a file through Parser to DocumentModel +* [ ] I understand SHA1-based incremental builds +* [ ] I can create custom document types with templates +* [ ] I comprehend the difference between markup and template processing +* [ ] I've completed the hands-on exercise + +**Mentor Session:** + +* Review your custom document type implementation +* Discuss any parsing or template rendering issues +* Preview Week 3 topics (rendering and database queries) + +--- + +== Week 3: Deep Dive - Rendering & Database + +**Goal**: Master content rendering, database queries, and blog-specific features. + +=== Learning Objectives + +* [ ] Understand ContentStore (OrientDB wrapper) operations +* [ ] Write ContentStore queries for tag pages, archives +* [ ] Comprehend RenderingTool SPI pattern +* [ ] Explain pagination mechanism +* [ ] Debug template rendering issues + +=== Activities + +==== Day 1-2: ContentStore & OrientDB + +1. **Study Database Layer** + * Read: `jbake-core/src/main/java/org/jbake/app/ContentStore.java` + * Read: `jbake-core/src/main/java/org/jbake/app/DBUtil.java` + * Read: `jbake-core/src/main/java/org/jbake/app/DocumentList.java` + +2. **Understand Query Methods** + * `getPublishedPosts()` - how does it work? + * `getPublishedPostsByTag()` - tag filtering + * `getAllTags()` - aggregation + * SQL-like syntax: `"select * from Documents where status='published' and type='post' order by date desc"` + +3. **Experiment with Queries** + * Add breakpoint in `ContentStore.getPublishedPosts()` + * Run tests: `ContentStoreTest.java` + * Use OrientDB Studio (if plocal mode) to inspect schema + +==== Day 3: RenderingTool Architecture + +1. **Study SPI Pattern** + * Read: `jbake-core/src/main/java/org/jbake/render/RenderingTool.java` (interface) + * Read META-INF/services: `org.jbake.render.RenderingTool` + * Read implementations: + - `IndexRenderer.java` + - `ArchiveRenderer.java` + - `TagsRenderer.java` + - `FeedRenderer.java` + +2. **Understand Rendering Flow** + * `Oven.renderContent()` uses ServiceLoader + * Each RenderingTool gets Renderer, ContentStore, Configuration + * Tools query ContentStore, render templates, write HTML files + * Return count of rendered documents + +==== Day 4-5: Blog Features Implementation + +1. **Study Tag Pages** + * Read: `TagsRenderer.java` - how tags are extracted and rendered + * Read template: `fixture/freemarkerTemplates/tags.ftl` + * Trace: content with `tags=java,tutorial` → tag page generation + +2. **Study Archives** + * Read: `ArchiveRenderer.java` - date-based organization + * Examine how posts grouped by date + +3. **Study RSS/Atom Feed** + * Read: `FeedRenderer.java` + * Template: `feed.ftl` generates XML + +4. **Implement Custom RenderingTool** + * Create class: `AuthorRenderer` (groups posts by author) + * Implement `RenderingTool` interface + * Add to META-INF/services + * Create template: `author.ftl` + * Test with example content + +=== Validation Questions + +1. **What does ContentStore do?** + * Expected: Wraps OrientDB, provides content caching and querying, SHA1-based change detection + +2. **How does JBake generate tag pages?** + * Expected: TagsRenderer queries all tags, for each tag queries posts with that tag, renders template + +3. **What is the RenderingTool interface for?** + * Expected: SPI for custom renderers (feeds, sitemaps, archives); discovered via ServiceLoader + +4. **Explain how pagination works in JBake.** + * Expected: Renderer calculates total pages, renders each page with subset of content, links to prev/next + +5. **Where are SQL queries defined?** + * Expected: As String constants in ContentStore class (e.g., STATEMENT_GET_PUBLISHED_POST_BY_TYPE_AND_TAG) + +=== Hands-On Exercise + +**Implement "Category Archive" Feature:** + +1. Add `category` field support to DocumentModel +2. Create `CategoryRenderer` extending RenderingTool +3. Query posts by category from ContentStore +4. Create template `category.ftl` to display category pages +5. Register via META-INF/services +6. Test with example content +7. Submit for mentor review + +=== Week 3 Checkpoint + +**Self-Assessment:** + +* [ ] I understand ContentStore queries and OrientDB integration +* [ ] I can implement a custom RenderingTool +* [ ] I comprehend blog features (tags, archives, feeds) +* [ ] I've debugged template rendering successfully +* [ ] Hands-on exercise completed + +**Mentor Session:** + +* Code review of CategoryRenderer implementation +* Discuss OrientDB query optimization +* Performance testing discussion +* Preview Week 4 (independence tasks) + +--- + +== Week 4: Independence - Contributing & Extending + +**Goal**: Work independently on real issues, understand contribution workflow, and extend JBake. + +=== Learning Objectives + +* [ ] Navigate GitHub issues and select appropriate tasks +* [ ] Follow contribution guidelines +* [ ] Run full test suite and add new tests +* [ ] Submit quality pull requests +* [ ] Understand release process + +=== Activities + +==== Day 1: Contribution Workflow + +1. **Read Contribution Guide** + * `CONTRIBUTING.asciidoc` - full guidelines + * Code style and formatting + * Test requirements + * PR submission process + +2. **Setup Development Environment** + * Configure IDE with checkstyle: `config/checkstyle/checkstyle.xml` + * Run tests: `./gradlew test` + * Run checkstyle: `./gradlew check` + * Build distribution: `./gradlew distZip` + +3. **Study Test Structure** + * Unit tests: `jbake-core/src/test/java/` + * Integration tests: `ContentStoreIntegrationTest.java` + * Fixtures: `jbake-core/src/test/resources/fixture/` + * Template engine tests: `AbstractTemplateEngineRenderingTest.java` + +==== Day 2-3: Pick and Implement an Issue + +1. **Select Issue** + * Browse: https://github.com/jbake-org/jbake/issues + * Filter by labels: `good first issue`, `help wanted` + * Example types: + - Bug fix (parser error, rendering issue) + - Enhancement (new config option, template helper) + - Documentation (JavaDoc, user guide) + +2. **Implementation Process** + * Fork repository + * Create feature branch: `feature/issue-123-description` + * Implement fix/feature + * Add tests (required!) + * Run full test suite + * Run checkstyle + * Commit with message: `Fix #123: Description` + +3. **Code Review Preparation** + * Self-review your changes + * Check for: + - Backwards compatibility + - Test coverage + - Javadoc for public APIs + - No breaking changes without deprecation + +==== Day 4: Pull Request Submission + +1. **Create PR** + * Push branch to your fork + * Open PR on GitHub + * Fill in PR template: + - Description of change + - Issue reference + - Testing performed + - Breaking changes (if any) + +2. **Respond to Review** + * Address review comments + * Update tests if needed + * Discuss design decisions + +==== Day 5: Advanced Topics + +Choose one deep-dive topic: + +**Option A: Performance Optimization** + +* Profile JBake with YourKit/VisualVM +* Identify bottlenecks (parsing, rendering, DB queries) +* Implement optimization +* Benchmark before/after + +**Option B: New Template Engine** + +* Implement support for new template engine (e.g., Velocity, Mustache) +* Extend `AbstractTemplateEngine` +* Add to `TemplateEngines` registry +* Create example templates +* Write integration tests + +**Option C: Build Tool Plugin** + +* Study jbake-maven-plugin code +* Create Gradle plugin equivalent +* Or enhance existing Maven plugin + +=== Validation Questions + +1. **What's the contribution workflow?** + * Expected: Fork → branch → implement → test → checkstyle → PR → review → merge + +2. **Where are integration tests located?** + * Expected: `jbake-core/src/test/java/`, extending `ContentStoreIntegrationTest` + +3. **What's required for a PR to be accepted?** + * Expected: Tests, checkstyle pass, backwards compatibility, no breaking changes (or deprecated), clear description + +4. **How would you add a new template engine?** + * Expected: Extend AbstractTemplateEngine, register in META-INF/services, add to TemplateEngines.properties, test + +5. **What's the release process?** + * Expected: Semantic versioning, tag, build artifacts, publish to Maven Central, GitHub release + +=== Final Project Options + +Choose one to demonstrate mastery: + +**Project A: Documentation Improvement** + +* Write tutorial: "Creating Custom Document Types" +* Add examples to jbake.org documentation +* Create video walkthrough of development workflow + +**Project B: Feature Implementation** + +* Implement small feature from backlog +* Full test coverage +* Documentation +* Submit PR + +**Project C: Performance Analysis** + +* Profile JBake on large site (1000+ pages) +* Create performance report +* Propose optimization strategies +* Implement one optimization + +=== Week 4 Checkpoint + +**Self-Assessment:** + +* [ ] I've successfully contributed a PR +* [ ] I can run and write tests independently +* [ ] I understand the release and versioning process +* [ ] I've completed a deep-dive topic +* [ ] I can work on issues without constant guidance + +**Final Mentor Session:** + +* Review final project +* Discuss areas for continued growth +* Identify specialization areas (parsers, templates, performance, etc.) +* Transition to regular contributor status + +--- + +== Post-Onboarding: Continued Learning + +=== Specialization Paths + +**Path 1: Core Architecture** + +* Refactor legacy code to modern patterns +* Improve error handling and logging +* Performance optimization +* OrientDB migration strategy + +**Path 2: Parser Ecosystem** + +* Maintain markup engines (Markdown, AsciiDoc) +* Add new format support +* Parser performance optimization + +**Path 3: Template Engines** + +* Maintain template engine integrations +* Help users with template issues +* Create template helpers and utilities + +**Path 4: Documentation & Community** + +* Maintain jbake.org site +* Write tutorials and guides +* Answer forum/Gitter questions +* Create example projects + +=== Resources for Continued Learning + +* **API Reference**: Javadoc in code +* **User Documentation**: http://jbake.org/docs/ +* **Community**: + - Mailing List: https://groups.google.com/group/jbake-dev + - Forum: https://groups.google.com/group/jbake-user + - Gitter: https://gitter.im/jbake-org/jbake +* **Related Projects**: + - DocToolchain: https://doctoolchain.org/ + - JBake Maven Plugin: jbake-maven-plugin/ + - Gradle Plugin: Community maintained + +=== Success Criteria + +You've successfully onboarded when you can: + +* ✅ Independently select and complete issues +* ✅ Review other contributors' PRs +* ✅ Answer user questions on forum/Gitter +* ✅ Explain architecture decisions to new contributors +* ✅ Propose new features with design rationale +* ✅ Maintain backwards compatibility +* ✅ Write clear, tested, documented code + +Welcome to the JBake team! 🎉 diff --git a/docs/open-questions.adoc b/docs/open-questions.adoc new file mode 100644 index 000000000..586dfd467 --- /dev/null +++ b/docs/open-questions.adoc @@ -0,0 +1,509 @@ += Open Questions & Clarifications Needed + +This document tracks ambiguities, missing information, and areas needing clarification from the JBake team. + +**Purpose:** Identify gaps in understanding during mental model documentation creation. These questions highlight areas where: + +* Documentation is incomplete or missing +* Code behavior is unclear +* Design decisions are not documented +* Implementation differs from expected patterns + +--- + +== Architecture & Design + +=== Q1: DocumentTypes Global Singleton + +**Question:** Why is `DocumentTypes` implemented as a mutable global singleton instead of being configuration-driven or passed as a parameter? + +**Context:** + +* `DocumentTypes` is a static class with mutable state +* `addDocumentType()` and `resetDocumentTypes()` mutate global state +* Thread-safety concerns for concurrent builds +* Hard to test (state leaks between tests) + +**Impact:** + +* Refactoring to instance-based would be significant change +* Current pattern violates dependency injection principles +* Need to understand if historical reasons or planned refactoring exists + +**Found in:** `jbake-core/src/main/java/org/jbake/model/DocumentTypes.java` + +**Assumption made:** This is technical debt from early implementation; future versions may refactor to configuration-based approach. + +--- + +=== Q2: OrientDB Version Migration Strategy + +**Question:** What's the long-term strategy for OrientDB? Stay on v3.x, migrate to v4+, or replace with different database? + +**Context:** + +* OrientDB v3.x has significantly different API from v2.x +* OrientDB development/community activity unclear +* Heavy dependency for what's essentially a cache +* Comments in code reference version migration issues + +**Impact:** + +* Future maintenance burden +* Alternative database choices (H2, SQLite, in-memory maps) +* Should new features depend heavily on OrientDB features? + +**Found in:** `jbake-core/src/main/java/org/jbake/app/ContentStore.java`, OrientDB dependency in `build.gradle` + +**Assumption made:** Staying on OrientDB v3.x for now; replacement evaluated case-by-case. + +--- + +=== Q3: Utensils Factory Pattern Rationale + +**Question:** Why is the factory named "Utensils"? Is this just following the bakery metaphor or is there a deeper design reason? + +**Context:** + +* `UtensilsFactory` creates Crawler, Parser, Renderer, Asset, ContentStore +* Name is unique but not immediately intuitive for developers +* Standard factory pattern but unusual naming + +**Impact:** + +* Naming impacts code readability +* Developer onboarding (need to learn metaphor) +* Should metaphor naming extend to other areas? + +**Found in:** `jbake-core/src/main/java/org/jbake/app/Utensils.java` + +**Assumption made:** Utensils = tools used in baking = components used by Oven. Metaphor aids understanding once learned. + +--- + +== Configuration & Behavior + +=== Q4: Template Signature Mechanism + +**Question:** How exactly does template signature detection work? When does template change force content re-rendering without re-parsing? + +**Context:** + +* Code mentions "template signature" separate from content SHA1 +* `ContentStore.updateAndClearCacheIfNeeded()` checks template changes +* `updateTemplateSignatureIfChanged()` computes signature +* Mechanism not clearly documented + +**Impact:** + +* Users confused about when `-clearCache` needed +* Incremental build behavior not transparent + +**Found in:** `ContentStore.java` methods related to signatures + +**Assumption made:** SHA1 of all template files; if changed, marks all content for re-render but not re-parse. + +--- + +=== Q5: Default Status/Type Behavior + +**Question:** What's the exact precedence when default status/type conflicts with header-less documents? + +**Context:** + +* `default.status` and `default.type` in config +* Documents can omit headers (AsciiDoc native format) +* What happens if default.status=published but user wants draft? + +**Impact:** + +* User confusion about why content appears/doesn't appear +* Edge cases not documented + +**Found in:** Parser implementations, configuration handling + +**Assumption made:** Header explicit values override defaults; absence uses defaults; documented but could be clearer. + +--- + +=== Q6: Asset Ignore Patterns + +**Question:** What's the syntax and precedence for asset ignore patterns? Does `.jbakeignore` apply to assets or only content? + +**Context:** + +* `asset.ignore` configuration option +* `.jbakeignore` files in directories +* Asset copying logic in `Asset.java` +* Docs unclear on pattern syntax + +**Impact:** + +* Users can't exclude node_modules, .git, etc. effectively +* Performance issues on large asset folders + +**Found in:** `jbake-core/src/main/java/org/jbake/app/Asset.java`, configuration docs + +**Assumption made:** `.jbakeignore` affects both content and assets; contains directory/file names to skip. + +--- + +== Extension & Plugin System + +=== Q7: RenderingTool Execution Order + +**Question:** Can RenderingTools specify execution order/priority? What if one tool depends on output of another? + +**Context:** + +* ServiceLoader provides no ordering guarantees +* Some renderers might depend on others (e.g., sitemap depends on all pages existing) +* No Priority interface visible in code + +**Impact:** + +* Plugin authors can't ensure dependencies +* Potential for race conditions or missing data + +**Found in:** `Oven.renderContent()`, ServiceLoader usage + +**Assumption made:** Currently unordered; tools should be independent; if ordering needed, implement externally. + +--- + +=== Q8: Template Engine Extension Registration + +**Question:** How does extension → engine mapping work exactly? Can users override mappings? + +**Context:** + +* `META-INF/org.jbake.parser.TemplateEngines.properties` maps extensions +* Format: `classname=ext1,ext2,ext3` +* Can user override in their project? +* What if multiple engines claim same extension? + +**Impact:** + +* Custom template engine development +* Conflict resolution strategy + +**Found in:** `TemplateEngines.java`, properties files + +**Assumption made:** First-registered wins; user can't override without custom JAR. + +--- + +== Performance & Optimization + +=== Q9: ContentStore Memory Usage + +**Question:** What's the memory footprint of ContentStore for large sites (10K+ pages)? Are there limits? + +**Context:** + +* OrientDB embedded can be memory or plocal +* Memory mode loads everything +* Plocal mode uses disk but still has memory cache +* No documented limits or recommendations + +**Impact:** + +* Site size recommendations +* Memory tuning guidance +* When to use memory vs. plocal + +**Found in:** ContentStore initialization, DB configuration + +**Assumption made:** Memory mode for < 1000 pages, plocal for larger sites; no hard limits documented. + +--- + +=== Q10: Parallel Processing Potential + +**Question:** Has parallel processing been considered? Can stages or file processing be parallelized? + +**Context:** + +* Current pipeline is sequential +* Many files are independent (could parse in parallel) +* Rendering could be parallelized +* No threading visible in code + +**Impact:** + +* Performance on multi-core systems +* Large site build times + +**Found in:** `Oven.java`, `Crawler.java` + +**Assumption made:** Sequential for simplicity; parallelization future enhancement. + +--- + +## Testing & Quality + +=== Q11: Integration Test Coverage + +**Question:** What's the test coverage strategy? Are there coverage targets? + +**Context:** + +* Many integration tests present +* Some core classes well-tested +* Coverage reports not visible +* SPI extensions testing unclear + +**Impact:** + +* Confidence in refactoring +* Regression prevention + +**Found in:** Test suite + +**Assumption made:** "Good enough" coverage; no strict percentage target. + +--- + +=== Q12: Browser-Based Testing + +**Question:** Is there testing of generated HTML in actual browsers? Visual regression testing? + +**Context:** + +* Tests verify files generated +* Content correctness checked +* No browser rendering tests visible +* HTML structure tested but not rendering + +**Impact:** + +* CSS/JS integration issues +* Cross-browser compatibility + +**Found in:** Test suite + +**Assumption made:** Out of scope; users responsible for template browser testing. + +--- + +## Error Handling & Debugging + +=== Q13: Error Aggregation Strategy + +**Question:** Why collect errors instead of fail-fast? What's the rationale? + +**Context:** + +* `Oven` collects errors in List +* Continues processing after errors +* Final check throws JBakeException with summary +* Different from typical fail-fast + +**Impact:** + +* User experience (see all errors vs. one at a time) +* Error recovery complexity + +**Found in:** `Oven.bake()` error handling + +**Assumption made:** Better UX for large sites (fix all errors at once); design trade-off. + +--- + +=== Q14: Logging Configuration + +**Question:** How do users customize logging? SLF4J configuration not documented. + +**Context:** + +* JBake uses SLF4J +* Tests use Logback +* User projects - what logger binding? +* Configuration file location/format? + +**Impact:** + +* Debugging production issues +* Performance troubleshooting (verbose logging) + +**Found in:** Logging usage throughout codebase + +**Assumption made:** Users provide own SLF4J binding (Logback, Log4j2, etc.); configuration standard for chosen binding. + +--- + +## Migration & Compatibility + +=== Q15: Java Version Support Policy + +**Question:** What triggers minimum Java version increase? When will Java 8 support be dropped? + +**Context:** + +* Currently Java 8+ +* Java 8 EOL passed (2019) +* Modern features (modules, records, etc.) unavailable +* Maintenance burden of old version + +**Impact:** + +* Adoption of new Java features +* Dependency updates (libraries require newer Java) +* User migration planning + +**Found in:** Build configuration, documentation + +**Assumption made:** Java 8 support continues until major version bump (3.0.0); ADR needed. + +--- + +=== Q16: Breaking Change Policy + +**Question:** When are breaking changes acceptable? Only major versions? + +**Context:** + +* Semantic versioning declared in README +* Many @Deprecated methods +* Unclear when deprecations can be removed + +**Impact:** + +* Upgrade path clarity +* User migration planning + +**Found in:** Version history, deprecation comments + +**Assumption made:** Breaking changes only in major versions (X.0.0); deprecations removed after one major version cycle. + +--- + +## Documentation Gaps + +=== Q17: User Documentation vs. Developer Documentation + +**Question:** What's in scope for different doc types? Where should detailed API docs live? + +**Context:** + +* jbake.org has user docs +* arc42 docs (this directory) for architecture +* Javadoc in code +* Overlap and gaps + +**Impact:** + +* Documentation maintenance +* User vs. contributor guidance + +**Found in:** Various doc locations + +**Assumption made:** jbake.org = user; arc42 = architecture; Javadoc = API; GitHub wiki = community tips. + +--- + +=== Q18: Example Project Maintenance + +**Question:** Who maintains example projects? Are they tested in CI? + +**Context:** + +* Example ZIPs for each template engine +* May drift from current JBake version +* Breaking changes could break examples + +**Impact:** + +* New user first experience +* `-init` command reliability + +**Found in:** Example project resources + +**Assumption made:** Manually maintained; tested as part of smoke tests; should be in CI. + +--- + +## Prioritized Questions for Team Discussion + +**HIGH PRIORITY:** + +1. Q2: OrientDB strategy - impacts future development +2. Q7: RenderingTool ordering - affects plugin ecosystem +3. Q15: Java version policy - affects maintainability + +**MEDIUM PRIORITY:** + +4. Q4: Template signature mechanism - user confusion +5. Q6: Asset ignore patterns - common use case +6. Q13: Error aggregation rationale - document for newcomers + +**LOW PRIORITY:** + +7. Q3: Utensils naming - nice to know, not blocking +8. Q11: Test coverage strategy - internal decision +9. Q18: Example maintenance - process question + +--- + +## How to Use This Document + +**For maintainers:** + +* Review questions, provide answers +* Turn answers into documentation or ADRs +* Mark resolved questions +* Track action items + +**For contributors:** + +* Understand known gaps +* Don't assume when questions exist +* Add new questions discovered during work +* Help answer based on code archaeology + +**Format for answering:** + +``` +**Q#: Question Title** + +✅ **RESOLVED** + +**Answer:** [Clear explanation] + +**Documented in:** [Link to doc/ADR/code comment] + +**Resolved by:** @username on DATE +``` + +--- + +## Meta: Questions About This Documentation + +### Q19: Mental Model Documentation Completeness + +**Question:** Is this mental model documentation sufficient for new senior developers? What's missing? + +**Context:** Created comprehensive docs including: + +* Arc42 architecture sections +* ADRs with Pugh matrices +* Onboarding journey (4 weeks) +* LLM knowledge graph +* Anti-patterns catalog + +**Need feedback on:** + +* Clarity and accuracy +* Missing critical concepts +* Depth vs. breadth balance +* Maintainability + +**Action:** Schedule review with core team + +--- + +**Last Updated:** 2025-11-22 + +**Document Owner:** Architecture documentation team + +**Review Cycle:** Quarterly or after major releases diff --git a/dtcw b/dtcw new file mode 100755 index 000000000..5587b2055 --- /dev/null +++ b/dtcw @@ -0,0 +1,687 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MIT +# Copyright 2022 - 2023, Ralf D. Müller and the docToolchain contributors + +set -e +set -u +set -o pipefail + +# The main purpose of the wrapper script is to make 'docToolchain' easy to use. +# - it helps to install 'docToolchain' if not installed +# - you may manage different 'docToolchain' environments + +# See https://github.com/docToolchain/docToolchain/releases for available versions. +# Set DTC_VERSION to "latest" to get the latest, yet unreleased version. +: "${DTC_VERSION:=3.4.1}" + +# if not set, public docker hub is used +: "${DTC_DOCKER_PREFIX:=}" + +# The 'generateSite' and 'copyThemes' tasks support DTC_SITETHEME, an URL of a theme. +# export DTC_SITETHEME=https://....zip + +# The 'downloadTemplate' tasks uses DTC_TEMPLATE1, DTC_TEMPLATE2, ... +# with which you can specify the location of additional templates +# export DTC_TEMPLATE1=https://....zip +# export DTC_TEMPLATE2=https://....zip + +# docToolchain configuration file may be overruled by the user +: "${DTC_CONFIG_FILE:=docToolchainConfig.groovy}" + +# Contains the current project git branch, "-" if not available +: "${DTC_PROJECT_BRANCH:=$(git branch --show-current 2> /dev/null || echo -)}" +export DTC_PROJECT_BRANCH + +# DTC_HEADLESS is true if no STDIN is open (i.e. we have an non-interactive shell). +: "${DTC_HEADLESS:=$([ -t 0 ] && echo false || echo true)}" +export DTC_HEADLESS + +# Options passed to docToolchain +DTC_OPTS="${DTC_OPTS:-} -PmainConfigFile=${DTC_CONFIG_FILE} --warning-mode=none --no-daemon -Dfile.encoding=UTF-8 " + +# Here you find the project +GITHUB_PROJECT_URL=https://github.com/docToolchain/docToolchain +GITHUB_PROJECT_URL_SSH=git@github.com:docToolchain/docToolchain + +# Bump this version up if something is changed in the wrapper script +DTCW_VERSION=0.51 +# Template replaced by the GitHub value upon releasing dtcw +DTCW_GIT_HASH=##DTCW_GIT_HASH## + +# Exit codes +ERR_DTCW=1 +ERR_ARG=2 +ERR_CODING=3 + +main() { + # For debugging purpose at the top of the script + arch=$(uname -m) + os=$(uname -s) + bash=bash + + print_version_info + + assert_argument_exists "$@" + + [ "${1}" = "--version" ] && exit 0 + + available_environments=$(get_available_environments) + echo "Available docToolchain environments:${available_environments}" + + dtc_installations=$(get_dtc_installations "${available_environments}" "${DTC_VERSION}") + echo "Environments with docToolchain [${DTC_VERSION}]:${dtc_installations}" + + if is_supported_environment "${1}" && assert_environment_available "${1}" "${DTC_VERSION}"; then + # User enforced environment + environment=${1} + shift 1 + assert_argument_exists "$@" + else + environment=$(get_environment "$@") + fi + echo "Using environment: ${environment}" + + if [ "${1}" = "install" ]; then + install_component_and_exit "${environment}" "${2-}" + elif [ "${1}" = "getJava" ]; then + # TODO: remove getJava in the next major release + echo "Warning: 'getJava' is deprecated and will be removed. Use './dtcw install java' instead." + install_component_and_exit "${environment}" "java" + fi + + # No install command, so forward call to docToolchain but first we check if + # everything is there. + docker_image_name="" + docker_extra_arguments="" + if [[ ${environment} != docker ]]; then + assert_doctoolchain_installed "${environment}" "${DTC_VERSION}" + assert_java_version_supported + + # TODO: what if 'doctoolchain' found by $PATH does not match the one from the local environment? + # The version provided by $DTC_VERSION could be a different one. + else + docker_image_name="doctoolchain/doctoolchain" + if [ "${1}" = "image" ]; then + docker_image_name="${2-}" + shift 2 + assert_argument_exists "$@" + fi + echo "Using docker image: ${docker_image_name}" + if [ "${1}" = "extra_arguments" ]; then + docker_extra_arguments="${2-}" + shift 2 + assert_argument_exists "$@" + echo "Extra arguments passed to 'docker run' ${docker_extra_arguments}" + fi + fi + + command=$(build_command "${environment}" "${DTC_VERSION}" "${docker_image_name}" "${docker_extra_arguments}" "$@") + + [[ "${DTC_HEADLESS}" = true ]] && echo "Using headless mode since there is no (terminal) interaction possible" + + show_os_related_info + + emu="" + if [ "${os}" = "Darwin" ] && [ "${arch}" = "arm64" ]; then + echo "Apple silicon detected, using x86_64 mode and os native bash" + emu="/usr/bin/arch -x86_64" + bash="/bin/bash" + fi + + # echo "Command to invoke: ${command}" + # shellcheck disable=SC2086 + # as we hope to know what we are doing here ;-) + exec ${emu} ${bash} ${DTC_SHELL_DEBUG:-} -c "${command}" +} + +assert_argument_exists() { + if [[ $# -lt 1 ]]; then + error "argument missing" + usage + exit ${ERR_ARG} + fi +} + +error() { + printf "\nError: %s\n\n" "${1}" >&2 +} + +usage() { + cat < /dev/null || echo "unknown") + echo "docToolchain ${DTC_VERSION} - ${dtc_git_hash}" + else + echo "docToolchain ${DTC_VERSION}" + fi + echo "OS/arch: ${os}/${arch}" +} + +get_available_environments() { + # The local environment is alway available - even if docToolchain is not installed + local envs=" local" + + # 'sdk' is provided a bash founction - thus command doesn't work + if [ -n "${SDKMAN_DIR:-}" ] && [ -s "${SDKMAN_DIR}/bin/sdkman-init.sh" ]; then + envs+=" sdk" + fi + + if has docker; then + envs+=" docker" + fi + echo "${envs}" +} + +has() { + command -v "$1" 1>/dev/null 2>&1 +} + +get_dtc_installations() { + local envs=${1} + local version=${2} + local installations="" + + if [ -x "${DTC_HOME}/bin/doctoolchain" ]; then + installations+=" local" + else + # maybe it is just available in the path + if command -v doctoolchain >/dev/null 2>&1; then + installations+=" local" + fi + fi + + if [[ "${envs}" =~ sdk ]] && sdk_home_doctoolchain "${version}" &> /dev/null ; then + installations+=" sdk" + fi + + if [[ "${envs}" =~ docker ]]; then + # Having docker installed means docToolchain is available + installations+=" docker" + fi + [ -z "${installations}" ] && installations=" none" + + echo "${installations}" +} + +sdk_home_doctoolchain() { + local version=${1} + local sdk_doctoolchain="${SDKMAN_DIR}/candidates/doctoolchain/${version}" + + # Don't use `sdk home doctoolchain ${version}` - see https://github.com/sdkman/sdkman-cli/issues/1196 + if [[ -x "${sdk_doctoolchain}/bin/doctoolchain" ]]; then + printf "%s" "${sdk_doctoolchain}" + return 0 + fi + printf "doctoolchain %s is not installed on your system" "${version}" 1>&2 + return 1 +} + +is_supported_environment() { + local supported_environments=("local" "sdk" "docker") + [[ "${supported_environments[*]}" =~ ${1} ]] +} + +assert_environment_available() { + local env=${1} + local version=${2} + # If environment not available, exit with error + if ! is_environment_available "${env}" ; then + error "argument error - environment '${env}' not available" + if [[ "${env}" == "sdk" ]]; then + printf "%s\n" \ + "Install SDKMAN! (https://sdkman.io) with" \ + "" \ + " $ curl -s \"https://get.sdkman.io\" | bash" \ + "" \ + "Then open a new shell and install 'docToolchain' with" \ + " $ sdk install doctoolchain ${version}" + else + echo "Install 'docker' on your host to execute docToolchain in a container." + fi + echo + exit ${ERR_ARG} + fi + if is_doctoolchain_development_version "${version}" && [ "${env}" != local ] ; then + error "argument error - invalid environment '${env}'." + echo "Development version '${version}' can only be used in a local environment." + echo + exit ${ERR_ARG} + fi +} + +is_environment_available() { + [[ "${available_environments}" =~ ${1} ]] +} + +is_doctoolchain_development_version() { + local version=${1} + # Is 'latest' a good name? It maybe interpreted as latest stable release. + # Alternatives: 'testing', 'dev' (used for development) + [ "${version}" == "latest" ] || [ "${version}" == "latestdev" ] +} + +# No environment provided - try to pick the right one +get_environment() { + # 'install' works only with 'local' environment + if [[ "${1}" == install ]] || [[ "${dtc_installations}" =~ none ]]; then + echo local + return + fi + + # Pick the first one which has an installed docToolchain. + # Note: the preference is defined by the order we searched for available environments. + for e in ${available_environments}; do + if is_doctoolchain_installed "${e}"; then + echo "${e}" + return + fi + done +} + +is_doctoolchain_installed() { + [[ "${dtc_installations}" =~ ${1} ]] +} + +install_component_and_exit() { + local env=${1} + local component=${2} + if [ -z "${component}" ] ; then + error_install_component_and_die "component missing" + fi + exit_code=1 + case ${env} in + local) + case ${component} in + doctoolchain) + local_install_doctoolchain "${DTC_VERSION}" + assert_java_version_supported + ;; + java) + local_install_java + ;; + *) + error_install_component_and_die "unknown component '${component}'" + ;; + esac + if ! is_doctoolchain_installed "${environment}"; then + how_to_install_doctoolchain "${DTC_VERSION}" + else + printf "%s\n" \ + "" \ + "Use './dtcw tasks --group doctoolchain' to see docToolchain related tasks." \ + "" + fi + exit_code=0 + ;; + sdk) + error "argument error - '${env} install' not supported." + printf "%s\n" \ + "To install docToolchain with SDKMAN! execute" \ + "" \ + " $ sdk install doctoolchain ${DTC_VERSION}" \ + "" + exit_code=${ERR_ARG} + ;; + docker) + error "argument error - '${env} install' not supported." + echo "Executing a task in the 'docker' environment will pull the docToolchain container image." + echo + exit_code=${ERR_ARG} + ;; + *) + echo "Coding error - not reachable" + exit ${ERR_CODING} + ;; + esac + exit ${exit_code} +} + +error_install_component_and_die() { + error "${1} - available components are 'doctoolchain', or 'java'" + printf "%s\n" \ + "Use './dtcw local install doctoolchain' to install docToolchain ${DTC_VERSION}." \ + "Use './dtcw local install java' to install a Java version supported by docToolchain." \ + "" + exit ${ERR_ARG} +} + +local_install_doctoolchain() { + local version=${1} + if is_doctoolchain_development_version "${version}"; then + # User decided to pick a floating version - which means a git clone into the local environment. + assert_git_installed "Please install 'git' for working with a 'doctToolchain' development version" + + if [ -d "${DTC_HOME}/.git" ]; then + git -C "${DTC_HOME}" pull + echo "Updated docToolchain in local environment to latest version" + else + local project_url + if [ "${version}" == "latest" ]; then + project_url="${GITHUB_PROJECT_URL}" + else + project_url="${GITHUB_PROJECT_URL_SSH}" + fi + git clone "${project_url}.git" "${DTC_HOME}" + echo "Cloned docToolchain in local environment to latest version" + fi + else + if [ -x "${DTC_HOME}/bin/doctoolchain" ]; then + echo "Skipped installation of docToolchain: already installed in '${DTC_HOME}'" + return + fi + if ! has unzip; then + error "no unzip program installed, exiting…" + echo "Install 'unzip' and try to install docToolchain again." + return ${ERR_DTCW} + fi + mkdir -p "${DTC_ROOT}" + local distribution_url=${GITHUB_PROJECT_URL}/releases/download/v${version}/docToolchain-${version}.zip + download_file "${distribution_url}" "${DTC_ROOT}/source.zip" + unzip -q "${DTC_ROOT}/source.zip" -d "${DTC_ROOT}" + rm -f "${DTC_ROOT}/source.zip" + echo "Installed docToolchain successfully in '${DTC_HOME}'." + fi + + # Add it to the existing installations so the output to guide the user can adjust accordingly. + dtc_installations+=" local" +} + +assert_git_installed() { + has git && return + + error "git - command not found" + echo "${1}" + echo + exit ${ERR_DTCW} +} + +download_file() { + local url=${1} + local file=${2} + + if has curl; then + cmd="curl --fail --location --output $file $url" + elif has wget; then + # '--show-progress' is not supported in all wget versions (busybox) + cmd="wget --quiet --show-progress --output-document=$file $url" + elif has fetch; then + cmd="fetch --quiet --output=$file $url" + else + error "no HTTP download program (curl, wget, fetch) found, exiting…" + echo "Install either 'curl', 'wget', or 'fetch' and try to install docToolchain again." + return ${ERR_DTCW} + fi + + $cmd && return 0 || rc=$? + + error "Command failed (exit code $rc): ${cmd}" + return "${rc}" +} + +assert_java_version_supported() { + # Defines the order in which Java is searched. + if [ -d "${DTC_JAVA_HOME}" ]; then + echo "Caution: Your JAVA_HOME setting is overriden by DTCs own JDK install (for this execution)" + if [ -d "${DTC_JAVA_HOME}/Contents" ]; then + # JDK for MacOS have a different structure + JAVA_HOME="${DTC_JAVA_HOME}/Contents/Home" + else + JAVA_HOME="${DTC_JAVA_HOME}" + fi + export JAVA_HOME + DTC_OPTS="${DTC_OPTS} '-Dorg.gradle.java.home=${JAVA_HOME}'" + JAVA_CMD="${JAVA_HOME}/bin/java" + elif [ -n "${JAVA_HOME-}" ]; then + JAVA_CMD="${JAVA_HOME}/bin/java" + else + # Don't provide JAVA_HOME if java is used by PATH. + JAVA_CMD=$(command -v java 2>/dev/null) || true + fi + if [ -z "${JAVA_CMD-}" ] || ! java_version=$("${JAVA_CMD}" -version 2>&1) ; then + error "unable to locate a Java Runtime" + java_help_and_die + fi + + # We got a Java version + java_version=$(echo "$java_version" | awk -F '"' '/version/ {print $0}' | head -1 | cut -d'"' -f2) + major_version=$(echo "$java_version" | sed '/^1\./s///' | cut -d'.' -f1) + + if [ "${major_version}" -ge 11 ] && [ "${major_version}" -le 17 ]; then + echo "Using Java ${java_version} [${JAVA_CMD}]" + return + fi + + error "unsupported Java version ${major_version} [${JAVA_CMD}]" + java_help_and_die +} + +java_help_and_die() { + printf "%s\n" \ + "docToolchain supports Java versions 11, 14, or 17 (preferred). In case one of those" \ + "Java versions is installed make sure 'java' is found with your PATH environment" \ + "variable. As alternative you may provide the location of your Java installation" \ + "with JAVA_HOME." \ + "" \ + "Apart from installing Java with the package manager provided by your operating" \ + "system, dtcw facilitates the Java installation into a local environment:" \ + "" \ + " # Install Java in '${DTC_JAVA_HOME}'" \ + " $ ./dtcw local install java" \ + "" \ + "Alternatively you can use SDKMAN! (https://sdkman.io) to manage your Java installations" \ + "" + if ! is_environment_available sdk; then + how_to_install_sdkman "Java 17" + fi + # TODO: This will break when we change Java version + printf "%s\n" \ + " $ sdk install java 17.0.7-tem" \ + "" \ + "If you prefer not to install Java on your host, you can run docToolchain in a" \ + "docker container. For this case dtcw provides the 'docker' execution environment." \ + "" \ + "Example: ./dtcw docker generateSite"\ + "" + exit ${ERR_DTCW} +} + +how_to_install_sdkman() { + printf "%s\n" \ + " # First install SDKMAN!" \ + " $ curl -s \"https://get.sdkman.io\" | bash" \ + " # Then open a new shell and install ${1} with" +} + +local_install_java() { + version=17 + implementation=hotspot + heapsize=normal + imagetype=jdk + releasetype=ga + + case "${arch}" in + x86_64) arch=x64 ;; + arm64) arch=aarch64 ;; + esac + if [[ ${os} == MINGW* ]]; then + error "MINGW64 is not supported" + echo "Please use powershell or WSL" + exit 1 + fi + case "${os}" in + Linux) os=linux ;; + Darwin) os=mac + # Enforce usage of Intel Java as long as jbake does not work on Apple Silicon + arch=x64 ;; + Cygwin) os=linux ;; + esac + mkdir -p "${DTC_JAVA_HOME}" + echo "Downloading JDK Temurin ${version} [${os}/${arch}] from Adoptium to ${DTC_JAVA_HOME}/jdk.tar.gz" + local adoptium_java_url="https://api.adoptium.net/v3/binary/latest/${version}/${releasetype}/${os}/${arch}/${imagetype}/${implementation}/${heapsize}/eclipse?project=jdk" + download_file "${adoptium_java_url}" "${DTC_JAVA_HOME}/jdk.tar.gz" + echo "Extracting JDK from archive file." + # TODO: should we not delete a previsouly installed on? + # Otherwise we may get a mix of different Java versions. + tar -zxf "${DTC_JAVA_HOME}/jdk.tar.gz" --strip 1 -C "${DTC_JAVA_HOME}/." + rm -f "${DTC_JAVA_HOME}/jdk/jdk.tar.gz" + + echo "Successfully installed Java in '${DTC_JAVA_HOME}'." + echo +} + +assert_doctoolchain_installed() { + local env=${1} + local version=${2} + if ! is_doctoolchain_installed "${env}"; then + # We reach this point if the user executes a command in an + # environment where docToolchain is not installed. + # Note that 'docker' always has a command (no instalation required) + error "doctoolchain - command not found [environment '${env}']" + how_to_install_doctoolchain "${version}" + exit ${ERR_DTCW} + fi +} + +how_to_install_doctoolchain() { + local version=${1} + printf "%s\n" \ + "It seems docToolchain ${version} is not installed. dtcw supports the" \ + "following docToolchain environments:" \ + "" \ + "1. 'local': to install docToolchain in [${DTC_ROOT}] use" \ + "" \ + " $ ./dtcw local install doctoolchain" \ + "" \ + "2. 'sdk': to install docToolchain with SDKMAN! (https://sdkman.io)" \ + "" + if ! is_environment_available sdk; then + how_to_install_sdkman docToolchain + fi + printf "%s\n" \ + " \$ sdk install doctoolchain ${version}" \ + "" \ + "Note that running docToolchain in 'local' or 'sdk' environment needs a" \ + "Java runtime (major version 11, 14, or 17) installed on your host." \ + "" \ + "3. 'docker': pull the docToolchain image and execute docToolchain in a container environment." \ + "" \ + " \$ ./dtcw docker tasks --group doctoolchain" \ + "" +} + +build_command() { + local env=${1} + local version=${2} + local docker_image=${3} + local docker_extra_arguments=${4} + shift 4 + local cmd + if [ "${env}" = docker ]; then + # TODO: DTC_PROJECT_BRANCH is not passed into the docker environment + # See https://github.com/docToolchain/docToolchain/issues/1087 + + local container_name=doctoolchain-${version} + container_name+="-$(date '+%Y%m%d_%H%M%S')" + pwd=$(has cygpath && cygpath -w "${PWD}" || echo "${PWD}") + + docker_env_file=dtcw_docker.env + env_file_option="" + if [ -f "$docker_env_file" ]; then + env_file_option="--env-file ${docker_env_file}" + fi + + docker_args="run --rm -i --platform linux/amd64 -u $(id -u):$(id -g) --name ${container_name} \ + -e DTC_HEADLESS=true -e DTC_SITETHEME -e DTC_PROJECT_BRANCH=${DTC_PROJECT_BRANCH} \ + ${docker_extra_arguments} ${env_file_option} \ + --entrypoint /bin/bash -v '${pwd}:/project' ${DTC_DOCKER_PREFIX}${docker_image}:v${version}" + + cmd="docker ${docker_args} -c \"doctoolchain . ${*} ${DTC_OPTS} && exit\"" + else + # TODO: What is this good for? Has probably to do with Docker image creation. + if [[ "${DTC_HEADLESS}" = false ]]; then + # important for the docker file to find dependencies + DTC_OPTS="${DTC_OPTS} '-Dgradle.user.home=${DTC_ROOT}/.gradle'" + fi + if [ "${env}" = local ]; then + if [ -x "${DTC_HOME}/bin/doctoolchain" ]; then + cmd="${DTC_HOME}/bin/doctoolchain" + # is doctoolchain available on the path? + elif command -v doctoolchain >/dev/null 2>&1; then + cmd="doctoolchain" + else + echo "Could not find doctoolchain executable, neither in '${DTC_HOME}' nor on PATH ('${PATH}')" >&2 + exit 1 + fi + cmd="${cmd} . ${*} ${DTC_OPTS}" + else + cmd="$(sdk_home_doctoolchain "${version}")/bin/doctoolchain . ${*} ${DTC_OPTS}" + fi + fi + echo "${cmd}" +} + +show_os_related_info() { + + # check if we are running on WSL + if grep -qsi 'microsoft' /proc/version &> /dev/null ; then + # TODO: bug - This URL leads to now-where! + printf "%s\n" \ + "" \ + "Bash is running on WSL which might cause problems with plantUML." \ + "See https://doctoolchain.org/docToolchain/v2.0.x/010_manual/50_Frequently_asked_Questions.html#wsl for more details." \ + "" + fi +} + +# Location where local installations are placed. +DTC_ROOT="${HOME}/.doctoolchain" + +# More than one docToolchain version may be installed locally. +# This is the directory for the specific version. +DTC_HOME="${DTC_ROOT}/docToolchain-${DTC_VERSION}" + +# Directory for local Java installation +DTC_JAVA_HOME="${DTC_ROOT}/jdk" + +main "$@"