This is an automated email from the ASF dual-hosted git repository. sergehuber pushed a commit to branch UNOMI-932-unomi3-major-release-story in repository https://gitbox.apache.org/repos/asf/unomi-site.git
commit 99ac7ead4297964cf79b3379d6b8b3972eba75a2 Author: Serge Huber <[email protected]> AuthorDate: Wed Jul 15 09:50:09 2026 +0200 Add Unomi 3 quality/ops blog wave with SVGs (UNOMI-932) Ship platform, OpenSearch, unit harness, shell, scheduler, tracing, OpenAPI, IT/manual/build.sh posts plus index/sitemap wiring and code-block contrast fixes. --- src/main/webapp/assets/css/unomi.css | 24 ++- src/main/webapp/assets/images/unomi3/build-sh.svg | 89 ++++++++ .../images/unomi3/it-progress-before-after.svg | 123 +++++++++++ .../assets/images/unomi3/manual-plantuml-theme.svg | 94 +++++++++ .../assets/images/unomi3/openapi-enrichment.svg | 77 +++++++ .../assets/images/unomi3/opensearch-dual.svg | 67 ++++++ .../assets/images/unomi3/platform-foundation.svg | 73 +++++++ .../assets/images/unomi3/request-tracing.svg | 65 ++++++ .../webapp/assets/images/unomi3/shell-commands.svg | 80 ++++++++ .../webapp/assets/images/unomi3/task-scheduler.svg | 71 +++++++ .../assets/images/unomi3/unit-harness-vs-it.svg | 18 +- src/main/webapp/blog/index.html | 225 ++++++++++++++++++++- src/main/webapp/blog/unomi-3-build-sh.html | 79 ++++++++ src/main/webapp/blog/unomi-3-it-execution.html | 96 +++++++++ src/main/webapp/blog/unomi-3-manual.html | 102 ++++++++++ .../webapp/blog/unomi-3-openapi-enrichment.html | 70 +++++++ src/main/webapp/blog/unomi-3-opensearch.html | 75 +++++++ .../webapp/blog/unomi-3-platform-foundation.html | 50 +++++ src/main/webapp/blog/unomi-3-request-tracing.html | 168 +++++++++++++++ src/main/webapp/blog/unomi-3-shell-commands.html | 116 +++++++++++ src/main/webapp/blog/unomi-3-task-scheduler.html | 102 ++++++++++ .../webapp/blog/unomi-3-unit-testing-harness.html | 84 ++++++++ src/main/webapp/sitemap.xml | 50 +++++ 23 files changed, 1986 insertions(+), 12 deletions(-) diff --git a/src/main/webapp/assets/css/unomi.css b/src/main/webapp/assets/css/unomi.css index b1abb69..6e9de89 100644 --- a/src/main/webapp/assets/css/unomi.css +++ b/src/main/webapp/assets/css/unomi.css @@ -598,8 +598,8 @@ pre, code { } pre { - background: var(--unomi-text); - color: var(--unomi-border); + background: var(--unomi-text); /* slate-800 */ + color: #f1f5f9; /* high-contrast light slate */ border-radius: var(--unomi-radius-sm); padding: 1.25rem; font-size: 0.85rem; @@ -608,6 +608,26 @@ pre { -webkit-overflow-scrolling: touch; } +pre code { + color: inherit; + background: transparent; + font-size: inherit; + padding: 0; +} + +/* Light surface variant (opt-in) — keep text dark for contrast */ +pre.code-block-light, +pre.bg-light { + background: var(--unomi-bg-soft) !important; + color: var(--unomi-text) !important; + border: 1px solid var(--unomi-border); +} + +pre.code-block-light code, +pre.bg-light code { + color: inherit; +} + code { font-size: 0.85em; } diff --git a/src/main/webapp/assets/images/unomi3/build-sh.svg b/src/main/webapp/assets/images/unomi3/build-sh.svg new file mode 100644 index 0000000..ff949f5 --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/build-sh.svg @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 380" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Unomi build.sh: one entry point for preflight, Maven build, integration tests, and deploy"> + <defs> +<style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + --term-bg: #0f172a; + --term-ok: #34d399; + font-family: Inter, system-ui, sans-serif; + } + @keyframes dash-flow { to { stroke-dashoffset: -24; } } + @keyframes pulse-soft { 0%,100%{opacity:.55} 50%{opacity:.9} } + @keyframes float-y { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} } + @keyframes glow { 0%,100%{filter:drop-shadow(0 0 4px rgba(109,92,231,.35))} 50%{filter:drop-shadow(0 0 10px rgba(109,92,231,.55))} } + .flow { stroke-dasharray: 6 5; animation: dash-flow 1.6s linear infinite; } + .pulse { animation: pulse-soft 3s ease-in-out infinite; } + .float { animation: float-y 3.2s ease-in-out infinite; } + .glow { animation: glow 3s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .flow,.pulse,.float,.glow { animation: none !important; } + } +</style> + </defs> + + <rect width="840" height="380" rx="20" fill="var(--unomi-bg-soft)"/> + <text x="40" y="40" fill="var(--unomi-text)" font-size="18" font-weight="700">One build entry point</text> + <text x="40" y="62" fill="var(--unomi-text-muted)" font-size="13">./build.sh replaces scattered scripts | same path locally and in CI</text> + + <rect x="300" y="100" width="240" height="72" rx="16" fill="var(--unomi-primary)" class="glow"/> + <text x="420" y="132" text-anchor="middle" fill="#ffffff" font-size="16" font-weight="700">./build.sh</text> + <text x="420" y="154" text-anchor="middle" fill="rgba(255,255,255,.85)" font-size="11">UNOMI-887 | build.ps1 twin</text> + + <g class="float"> + <rect x="40" y="210" width="170" height="120" rx="14" fill="var(--unomi-amber-bg)" stroke="var(--unomi-amber)" stroke-width="2"/> + <text x="125" y="242" text-anchor="middle" fill="var(--unomi-amber)" font-size="13" font-weight="700">Preflight</text> + <text x="125" y="268" text-anchor="middle" fill="var(--unomi-text)" font-size="11">Java | Maven | GraphViz</text> + <text x="125" y="290" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">memory | disk | ports</text> + <text x="125" y="312" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">clear failure output</text> + </g> + + <g class="float" style="animation-delay:.3s"> + <rect x="235" y="210" width="170" height="120" rx="14" fill="var(--unomi-info-bg)" stroke="var(--unomi-info)" stroke-width="2"/> + <text x="320" y="242" text-anchor="middle" fill="var(--unomi-info)" font-size="13" font-weight="700">Build</text> + <text x="320" y="268" text-anchor="middle" fill="var(--unomi-text)" font-size="11">install | -B -ntp in --ci</text> + <text x="320" y="290" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">distributions | ES / OS</text> + <text x="320" y="312" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">optional --javadoc</text> + </g> + + <g class="float" style="animation-delay:.6s"> + <rect x="430" y="210" width="170" height="120" rx="14" fill="var(--unomi-success-bg)" stroke="var(--unomi-success)" stroke-width="2"/> + <text x="515" y="242" text-anchor="middle" fill="var(--unomi-success)" font-size="13" font-weight="700">Test</text> + <text x="515" y="268" text-anchor="middle" fill="var(--unomi-text)" font-size="11">--integration-tests</text> + <text x="515" y="290" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">--single-test | --it-debug</text> + <text x="515" y="312" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">memory sampler</text> + </g> + + <g class="float" style="animation-delay:.9s"> + <rect x="625" y="210" width="175" height="120" rx="14" fill="var(--unomi-primary-light)" stroke="var(--unomi-primary)" stroke-width="2"/> + <text x="712" y="242" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="13" font-weight="700">Run / Deploy</text> + <text x="712" y="268" text-anchor="middle" fill="var(--unomi-text)" font-size="11">--deploy | --karaf-home</text> + <text x="712" y="290" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">--debug | --auto-start</text> + <text x="712" y="312" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">GitHub Actions --ci</text> + </g> + + <path d="M340 172 L 125 210" stroke="var(--unomi-primary)" stroke-width="2" class="flow"/> + <path d="M390 172 L 320 210" stroke="var(--unomi-primary)" stroke-width="2" class="flow"/> + <path d="M450 172 L 515 210" stroke="var(--unomi-primary)" stroke-width="2" class="flow"/> + <path d="M500 172 L 712 210" stroke="var(--unomi-primary)" stroke-width="2" class="flow"/> + + <rect x="40" y="345" width="760" height="22" rx="6" fill="var(--term-bg)"/> + <text x="56" y="360" fill="var(--term-ok)" font-size="11" font-family="ui-monospace, Menlo, monospace">$ ./build.sh --ci --integration-tests --use-opensearch</text> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/it-progress-before-after.svg b/src/main/webapp/assets/images/unomi3/it-progress-before-after.svg new file mode 100644 index 0000000..e7fb85e --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/it-progress-before-after.svg @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 420" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Before and after Unomi integration test console output: noisy downloads versus progress bar with ETA"> + <defs> +<style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-danger: #dc2626; + --unomi-danger-bg: #fef2f2; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + --term-bg: #0f172a; + --term-muted: #94a3b8; + --term-noise: #64748b; + --term-ok: #34d399; + --term-warn: #fbbf24; + font-family: Inter, system-ui, sans-serif; + } + @keyframes dash-flow { to { stroke-dashoffset: -24; } } + @keyframes pulse-soft { 0%,100%{opacity:.55} 50%{opacity:.9} } + @keyframes float-y { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} } + @keyframes bar-grow { + 0% { width: 42px; } + 50% { width: 118px; } + 100% { width: 42px; } + } + @keyframes scroll-noise { + 0% { transform: translateY(0); opacity: .9; } + 100% { transform: translateY(-28px); opacity: .9; } + } + .flow { stroke-dasharray: 6 5; animation: dash-flow 1.6s linear infinite; } + .pulse { animation: pulse-soft 3s ease-in-out infinite; } + .float { animation: float-y 3.2s ease-in-out infinite; } + .bar-fill { animation: bar-grow 4.5s ease-in-out infinite; } + .noise-scroll { animation: scroll-noise 3.8s linear infinite; } + @media (prefers-reduced-motion: reduce) { + .flow,.pulse,.float,.bar-fill,.noise-scroll { animation: none !important; } + } +</style> + <clipPath id="beforeClip"> + <rect x="56" y="148" width="328" height="196" rx="8"/> + </clipPath> + <clipPath id="afterClip"> + <rect x="456" y="148" width="328" height="196" rx="8"/> + </clipPath> + </defs> + + <rect width="840" height="420" rx="20" fill="var(--unomi-bg-soft)"/> + <text x="40" y="40" fill="var(--unomi-text)" font-size="18" font-weight="700">IT execution you can read</text> + <text x="40" y="62" fill="var(--unomi-text-muted)" font-size="13">Progress bars, quieter Maven/Docker noise, and signal when a long suite is moving</text> + + <g> + <rect x="40" y="92" width="360" height="280" rx="18" fill="var(--unomi-danger-bg)" stroke="var(--unomi-danger)" stroke-width="2"/> + <text x="60" y="122" fill="var(--unomi-danger)" font-size="14" font-weight="700">BEFORE</text> + <text x="140" y="122" fill="var(--unomi-text-muted)" font-size="12">transfer spam | no ETA</text> + + <rect x="56" y="138" width="328" height="212" rx="12" fill="var(--term-bg)"/> + <circle cx="74" cy="154" r="4" fill="#f87171"/> + <circle cx="88" cy="154" r="4" fill="#fbbf24"/> + <circle cx="102" cy="154" r="4" fill="#4ade80"/> + <text x="118" y="158" fill="var(--term-muted)" font-size="10" font-family="ui-monospace, SFMono-Regular, Menlo, monospace">ci | itests (old)</text> + + <g clip-path="url(#beforeClip)"> + <g class="noise-scroll" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="10"> + <text x="68" y="178" fill="var(--term-noise)">Downloading from central: junit.jar</text> + <text x="68" y="194" fill="var(--term-noise)">Progress (1): 4.1/384 kB</text> + <text x="68" y="210" fill="var(--term-noise)">Progress (1): 48/384 kB</text> + <text x="68" y="226" fill="var(--term-noise)">Progress (2): 128 kB | 3.2/512 kB</text> + <text x="68" y="242" fill="var(--term-noise)">Downloaded from central: junit.jar</text> + <text x="68" y="258" fill="var(--term-noise)">Downloading from central: hamcrest.jar</text> + <text x="68" y="274" fill="var(--term-noise)">Progress (1): 7.2/45 kB</text> + <text x="68" y="290" fill="var(--term-noise)">[INFO] --- docker:start (elasticsearch)</text> + <text x="68" y="306" fill="var(--term-noise)">... thousands of identical lines ...</text> + <text x="68" y="322" fill="var(--term-noise)">Downloading from central: bundle.jar</text> + <text x="68" y="338" fill="var(--term-noise)">Progress (1): 16/902 kB</text> + <text x="68" y="354" fill="var(--term-muted)">Tests run: ??? ETA: unknown</text> + </g> + </g> + </g> + + <g> + <rect x="440" y="92" width="360" height="280" rx="18" fill="var(--unomi-success-bg)" stroke="var(--unomi-success)" stroke-width="2"/> + <text x="460" y="122" fill="var(--unomi-success)" font-size="14" font-weight="700">AFTER</text> + <text x="528" y="122" fill="var(--unomi-text-muted)" font-size="12">progress | ETA | slowest CSV</text> + + <rect x="456" y="138" width="328" height="212" rx="12" fill="var(--term-bg)"/> + <circle cx="474" cy="154" r="4" fill="#f87171"/> + <circle cx="488" cy="154" r="4" fill="#fbbf24"/> + <circle cx="502" cy="154" r="4" fill="#4ade80"/> + <text x="518" y="158" fill="var(--term-muted)" font-size="10" font-family="ui-monospace, SFMono-Regular, Menlo, monospace">ci | itests (Unomi 3)</text> + + <g clip-path="url(#afterClip)" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="10"> + <text x="468" y="182" fill="var(--unomi-accent)">Apache Unomi | Integration tests</text> + <text x="468" y="200" fill="var(--term-muted)">Total tests: 412 | Search: Elasticsearch</text> + + <rect x="468" y="216" width="304" height="28" rx="6" fill="#1e293b" stroke="#334155"/> + <rect x="472" y="220" width="118" height="20" rx="4" fill="var(--term-ok)" class="bar-fill float"/> + <text x="478" y="234" fill="#0f172a" font-size="9" font-weight="700">38%</text> + + <text x="468" y="268" fill="var(--term-ok)">Progress: 38.11% (157/412)</text> + <text x="468" y="284" fill="var(--term-warn)">ETA: 18m 42s | OK: 157 Failed: 0</text> + <text x="468" y="310" fill="var(--term-muted)">Maven: -B -ntp | Docker logs to files</text> + <text x="468" y="326" fill="var(--unomi-hero-accent)">Top 10 slowest - CSV at finish</text> + </g> + </g> + + <path d="M 408 232 L 432 232" stroke="var(--unomi-primary)" stroke-width="2.5" stroke-linecap="round" class="flow"/> + <path d="M 424 224 L 436 232 L 424 240" fill="var(--unomi-primary)" class="pulse"/> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/manual-plantuml-theme.svg b/src/main/webapp/assets/images/unomi3/manual-plantuml-theme.svg new file mode 100644 index 0000000..eae593c --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/manual-plantuml-theme.svg @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 400" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Unomi 3 manual: AsciiDoc with PlantUML sources builds into a branded HTML manual with diagrams"> + <defs> +<style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + --term-bg: #0f172a; + --term-muted: #94a3b8; + --term-ok: #34d399; + font-family: Inter, system-ui, sans-serif; + } + @keyframes dash-flow { to { stroke-dashoffset: -24; } } + @keyframes pulse-soft { 0%,100%{opacity:.55} 50%{opacity:.9} } + @keyframes float-y { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} } + @keyframes glow { 0%,100%{filter:drop-shadow(0 0 4px rgba(109,92,231,.35))} 50%{filter:drop-shadow(0 0 10px rgba(109,92,231,.55))} } + .flow { stroke-dasharray: 6 5; animation: dash-flow 1.6s linear infinite; } + .pulse { animation: pulse-soft 3s ease-in-out infinite; } + .float { animation: float-y 3.2s ease-in-out infinite; } + .glow { animation: glow 3s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .flow,.pulse,.float,.glow { animation: none !important; } + } +</style> + </defs> + + <rect width="840" height="400" rx="20" fill="var(--unomi-bg-soft)"/> + <text x="40" y="40" fill="var(--unomi-text)" font-size="18" font-weight="700">Manual as code</text> + <text x="40" y="62" fill="var(--unomi-text-muted)" font-size="13">PlantUML in AsciiDoc | branded HTML/PDF | diagrams rebuild with the docs</text> + + <g> + <rect x="40" y="88" width="310" height="270" rx="18" fill="var(--unomi-info-bg)" stroke="var(--unomi-info)" stroke-width="2"/> + <text x="60" y="118" fill="var(--unomi-info)" font-size="14" font-weight="700">AsciiDoc source</text> + <rect x="56" y="136" width="278" height="200" rx="12" fill="var(--term-bg)"/> + <text x="72" y="162" fill="var(--unomi-accent)" font-size="11" font-family="ui-monospace, Menlo, monospace">[plantuml]</text> + <text x="72" y="182" fill="var(--term-muted)" font-size="10" font-family="ui-monospace, Menlo, monospace">----</text> + <text x="72" y="202" fill="var(--term-ok)" font-size="10" font-family="ui-monospace, Menlo, monospace">@startuml</text> + <text x="72" y="220" fill="#e2e8f0" font-size="10" font-family="ui-monospace, Menlo, monospace">!include C4_Component.puml</text> + <text x="72" y="238" fill="#e2e8f0" font-size="10" font-family="ui-monospace, Menlo, monospace">Container(unomi, "Unomi")</text> + <text x="72" y="256" fill="#e2e8f0" font-size="10" font-family="ui-monospace, Menlo, monospace">Component(rest, "REST")</text> + <text x="72" y="274" fill="var(--term-ok)" font-size="10" font-family="ui-monospace, Menlo, monospace">@enduml</text> + <text x="72" y="294" fill="var(--term-muted)" font-size="10" font-family="ui-monospace, Menlo, monospace">----</text> + <text x="72" y="318" fill="var(--unomi-hero-accent)" font-size="11" font-family="ui-monospace, Menlo, monospace">+ GraphViz | reviewable diffs</text> + </g> + + <g> + <path d="M 368 220 L 412 220" stroke="var(--unomi-primary)" stroke-width="2.5" stroke-linecap="round" class="flow"/> + <path d="M 404 212 L 416 220 L 404 228" fill="var(--unomi-primary)" class="pulse"/> + <text x="390" y="204" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="11" font-weight="600">build</text> + </g> + + <g class="float"> + <rect x="430" y="88" width="370" height="270" rx="18" fill="var(--unomi-primary-light)" stroke="var(--unomi-primary)" stroke-width="2" class="glow"/> + <text x="450" y="118" fill="var(--unomi-primary-dark)" font-size="14" font-weight="700">Branded manual</text> + <rect x="450" y="136" width="330" height="198" rx="12" fill="var(--unomi-white)" stroke="var(--unomi-border)"/> + <rect x="450" y="136" width="330" height="36" rx="12" fill="var(--unomi-primary)"/> + <rect x="450" y="158" width="330" height="14" fill="var(--unomi-primary)"/> + <text x="468" y="158" fill="#ffffff" font-size="11" font-weight="600">Apache Unomi 3.x | Documentation</text> + <rect x="458" y="184" width="88" height="140" rx="8" fill="var(--unomi-primary-subtle)"/> + <text x="468" y="204" fill="var(--unomi-primary-dark)" font-size="9" font-weight="700">Contents</text> + <text x="468" y="222" fill="var(--unomi-text-muted)" font-size="8">What's new</text> + <text x="468" y="238" fill="var(--unomi-text)" font-size="8" font-weight="600">Architecture</text> + <text x="468" y="254" fill="var(--unomi-text-muted)" font-size="8">Migrations</text> + <text x="468" y="270" fill="var(--unomi-text-muted)" font-size="8">Scheduler</text> + <text x="468" y="286" fill="var(--unomi-text-muted)" font-size="8">Security</text> + <rect x="560" y="184" width="100" height="64" rx="8" fill="var(--unomi-success-bg)" stroke="var(--unomi-success)"/> + <circle cx="590" cy="208" r="10" fill="var(--unomi-success)" opacity=".85"/> + <rect x="610" y="200" width="36" height="10" rx="3" fill="var(--unomi-success)" opacity=".5"/> + <rect x="610" y="216" width="28" height="10" rx="3" fill="var(--unomi-success)" opacity=".35"/> + <text x="610" y="238" fill="var(--unomi-success)" font-size="8" font-weight="600">C4 | PNG</text> + <rect x="672" y="184" width="96" height="64" rx="8" fill="var(--unomi-info-bg)" stroke="var(--unomi-info)"/> + <path d="M690 208 H 748 M 710 196 V 232" stroke="var(--unomi-info)" stroke-width="2" class="flow"/> + <circle cx="700" cy="208" r="5" fill="var(--unomi-info)"/> + <circle cx="748" cy="208" r="5" fill="var(--unomi-accent)"/> + <text x="700" y="238" fill="var(--unomi-info)" font-size="8" font-weight="600">sequence</text> + <text x="560" y="278" fill="var(--unomi-text)" font-size="11" font-weight="600">Inter | brand tokens</text> + <text x="560" y="296" fill="var(--unomi-text-muted)" font-size="10">Responsive nav + TOC | PDF theme</text> + <text x="560" y="314" fill="var(--unomi-primary-dark)" font-size="10" font-weight="600">62 PlantUML diagrams | text in git</text> + </g> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/openapi-enrichment.svg b/src/main/webapp/assets/images/unomi3/openapi-enrichment.svg new file mode 100644 index 0000000..d11d93a --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/openapi-enrichment.svg @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 400" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Unomi 3.1 OpenAPI enrichment: Javadoc packs merge into CXF openapi.json"> + <defs> +<style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + font-family: Inter, system-ui, sans-serif; + } + @keyframes dash-flow { to { stroke-dashoffset: -24; } } + @keyframes pulse-soft { 0%,100%{opacity:.55} 50%{opacity:.9} } + @keyframes float-y { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} } + @keyframes glow { 0%,100%{filter:drop-shadow(0 0 4px rgba(109,92,231,.35))} 50%{filter:drop-shadow(0 0 10px rgba(109,92,231,.55))} } + .flow { stroke-dasharray: 6 5; animation: dash-flow 1.6s linear infinite; } + .pulse { animation: pulse-soft 3s ease-in-out infinite; } + .float { animation: float-y 3.2s ease-in-out infinite; } + .glow { animation: glow 3s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .flow,.pulse,.float,.glow { animation: none !important; } + } +</style> + </defs> + <rect width="840" height="400" rx="20" fill="var(--unomi-bg-soft)"/> + <text x="40" y="40" fill="var(--unomi-text)" font-size="18" font-weight="700">Richer OpenAPI from Javadoc</text> + <text x="40" y="62" fill="var(--unomi-text-muted)" font-size="13">Unomi 3.1: build-time packs + runtime CXF merge (UNOMI-963)</text> + + <g class="float"> + <rect x="40" y="100" width="220" height="240" rx="14" fill="var(--unomi-amber-bg)" stroke="var(--unomi-amber)" stroke-width="2"/> + <text x="150" y="140" text-anchor="middle" fill="var(--unomi-amber)" font-size="13" font-weight="700">1. Sources</text> + <text x="150" y="172" text-anchor="middle" fill="var(--unomi-text)" font-size="12">JAX-RS endpoints</text> + <text x="150" y="198" text-anchor="middle" fill="var(--unomi-text)" font-size="12">@param / @return</text> + <text x="150" y="224" text-anchor="middle" fill="var(--unomi-text)" font-size="12">@api.status</text> + <text x="150" y="250" text-anchor="middle" fill="var(--unomi-text)" font-size="12">@api.example</text> + <text x="150" y="290" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">rest + extensions</text> + </g> + + <path d="M275 220 H325" stroke="var(--unomi-primary)" stroke-width="3" class="flow"/> + + <g class="float" style="animation-delay:.3s"> + <rect x="335" y="100" width="170" height="240" rx="14" fill="var(--unomi-primary)" class="glow"/> + <text x="420" y="160" text-anchor="middle" fill="#fff" font-size="13" font-weight="700">Maven Mojo</text> + <text x="420" y="190" text-anchor="middle" fill="rgba(255,255,255,.9)" font-size="11">enrichment pack</text> + <text x="420" y="220" text-anchor="middle" fill="rgba(255,255,255,.9)" font-size="11">META-INF/...</text> + <text x="420" y="250" text-anchor="middle" fill="rgba(255,255,255,.9)" font-size="11">openapi-doc.json</text> + <text x="420" y="300" text-anchor="middle" fill="rgba(255,255,255,.75)" font-size="11">per module JAR</text> + </g> + + <path d="M520 220 H570" stroke="var(--unomi-primary)" stroke-width="3" class="flow"/> + + <g class="float" style="animation-delay:.6s"> + <rect x="580" y="100" width="220" height="240" rx="14" fill="var(--unomi-success-bg)" stroke="var(--unomi-success)" stroke-width="2"/> + <text x="690" y="140" text-anchor="middle" fill="var(--unomi-success)" font-size="13" font-weight="700">2. Runtime</text> + <text x="690" y="172" text-anchor="middle" fill="var(--unomi-text)" font-size="12">CXF OpenApiFeature</text> + <text x="690" y="198" text-anchor="middle" fill="var(--unomi-text)" font-size="12">merge all packs</text> + <text x="690" y="224" text-anchor="middle" fill="var(--unomi-text)" font-size="12">/cxs/openapi.json</text> + <text x="690" y="262" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">summaries | statuses</text> + <text x="690" y="286" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">examples | schemas</text> + <text x="690" y="318" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="12" font-weight="600">Agent / IDE friendly</text> + </g> + +</svg> diff --git a/src/main/webapp/assets/images/unomi3/opensearch-dual.svg b/src/main/webapp/assets/images/unomi3/opensearch-dual.svg new file mode 100644 index 0000000..75321d9 --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/opensearch-dual.svg @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 380" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Unomi 3: Elasticsearch 9 and OpenSearch as dual persistence backends"> + <defs> +<style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + font-family: Inter, system-ui, sans-serif; + } + @keyframes dash-flow { to { stroke-dashoffset: -24; } } + @keyframes pulse-soft { 0%,100%{opacity:.55} 50%{opacity:.9} } + @keyframes float-y { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} } + @keyframes glow { 0%,100%{filter:drop-shadow(0 0 4px rgba(109,92,231,.35))} 50%{filter:drop-shadow(0 0 10px rgba(109,92,231,.55))} } + .flow { stroke-dasharray: 6 5; animation: dash-flow 1.6s linear infinite; } + .pulse { animation: pulse-soft 3s ease-in-out infinite; } + .float { animation: float-y 3.2s ease-in-out infinite; } + .glow { animation: glow 3s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .flow,.pulse,.float,.glow { animation: none !important; } + } +</style> + </defs> + <rect width="840" height="380" rx="20" fill="var(--unomi-bg-soft)"/> + <text x="40" y="40" fill="var(--unomi-text)" font-size="18" font-weight="700">One CDP, two search backends</text> + <text x="40" y="62" fill="var(--unomi-text-muted)" font-size="13">Unomi 3 ships Elasticsearch 9 and first-class OpenSearch distributions</text> + + <g class="float"> + <rect x="40" y="100" width="340" height="220" rx="16" fill="var(--unomi-info-bg)" stroke="var(--unomi-info)" stroke-width="2"/> + <text x="210" y="140" text-anchor="middle" fill="var(--unomi-info)" font-size="15" font-weight="700">Elasticsearch 9</text> + <text x="210" y="168" text-anchor="middle" fill="var(--unomi-text)" font-size="12">persistence-elasticsearch</text> + <text x="210" y="196" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="12">Java API Client (no HLRC)</text> + <text x="210" y="220" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="12">org.apache.unomi.elasticsearch.*</text> + <text x="210" y="252" text-anchor="middle" fill="var(--unomi-text)" font-size="12" font-weight="600">Default distribution</text> + <text x="210" y="280" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Pin e.g. 9.4.x in 3.1</text> + </g> + + <g class="float" style="animation-delay:.4s"> + <rect x="460" y="100" width="340" height="220" rx="16" fill="var(--unomi-success-bg)" stroke="var(--unomi-success)" stroke-width="2"/> + <text x="630" y="140" text-anchor="middle" fill="var(--unomi-success)" font-size="15" font-weight="700">OpenSearch</text> + <text x="630" y="168" text-anchor="middle" fill="var(--unomi-text)" font-size="12">persistence-opensearch</text> + <text x="630" y="196" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="12">Same PersistenceService SPI</text> + <text x="630" y="220" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="12">org.apache.unomi.opensearch.*</text> + <text x="630" y="252" text-anchor="middle" fill="var(--unomi-text)" font-size="12" font-weight="600">-Duse.opensearch=true</text> + <text x="630" y="280" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Migrate guide in the manual</text> + </g> + + <path d="M390 210 H450" stroke="var(--unomi-primary)" stroke-width="3" class="flow"/> + <circle cx="420" cy="210" r="18" fill="var(--unomi-primary)" class="glow"/> + <text x="420" y="215" text-anchor="middle" fill="#fff" font-size="10" font-weight="700">SPI</text> + +</svg> diff --git a/src/main/webapp/assets/images/unomi3/platform-foundation.svg b/src/main/webapp/assets/images/unomi3/platform-foundation.svg new file mode 100644 index 0000000..b20f50b --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/platform-foundation.svg @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 400" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Unomi 3 platform foundation: Java 17, Karaf 4.4, ES9, persistence clustering"> + <defs> +<style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + font-family: Inter, system-ui, sans-serif; + } + @keyframes dash-flow { to { stroke-dashoffset: -24; } } + @keyframes pulse-soft { 0%,100%{opacity:.55} 50%{opacity:.9} } + @keyframes float-y { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} } + @keyframes glow { 0%,100%{filter:drop-shadow(0 0 4px rgba(109,92,231,.35))} 50%{filter:drop-shadow(0 0 10px rgba(109,92,231,.55))} } + .flow { stroke-dasharray: 6 5; animation: dash-flow 1.6s linear infinite; } + .pulse { animation: pulse-soft 3s ease-in-out infinite; } + .float { animation: float-y 3.2s ease-in-out infinite; } + .glow { animation: glow 3s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .flow,.pulse,.float,.glow { animation: none !important; } + } +</style> + </defs> + <rect width="840" height="400" rx="20" fill="var(--unomi-bg-soft)"/> + <text x="40" y="40" fill="var(--unomi-text)" font-size="18" font-weight="700">Platform foundation (3.0 floor)</text> + <text x="40" y="62" fill="var(--unomi-text-muted)" font-size="13">The runtime that makes multi-tenancy and ops tooling possible</text> + + <g class="float"> + <rect x="40" y="100" width="175" height="120" rx="14" fill="var(--unomi-primary-light)" stroke="var(--unomi-primary)" stroke-width="2"/> + <text x="127" y="145" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="14" font-weight="700">Java 17+</text> + <text x="127" y="172" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Java 11 retired</text> + <text x="127" y="196" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">installer baseline</text> + </g> + <g class="float" style="animation-delay:.2s"> + <rect x="235" y="100" width="175" height="120" rx="14" fill="var(--unomi-info-bg)" stroke="var(--unomi-info)" stroke-width="2"/> + <text x="322" y="145" text-anchor="middle" fill="var(--unomi-info)" font-size="14" font-weight="700">Karaf 4.4</text> + <text x="322" y="172" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">OSGi runtime</text> + <text x="322" y="196" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">4.4.11 in 3.1</text> + </g> + <g class="float" style="animation-delay:.4s"> + <rect x="430" y="100" width="175" height="120" rx="14" fill="var(--unomi-amber-bg)" stroke="var(--unomi-amber)" stroke-width="2"/> + <text x="517" y="145" text-anchor="middle" fill="var(--unomi-amber)" font-size="14" font-weight="700">Search 9.x</text> + <text x="517" y="172" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">ES Java API client</text> + <text x="517" y="196" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">OpenSearch option</text> + </g> + <g class="float" style="animation-delay:.6s"> + <rect x="625" y="100" width="175" height="120" rx="14" fill="var(--unomi-success-bg)" stroke="var(--unomi-success)" stroke-width="2"/> + <text x="712" y="145" text-anchor="middle" fill="var(--unomi-success)" font-size="14" font-weight="700">Clustering</text> + <text x="712" y="172" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">No Cellar/Hazelcast</text> + <text x="712" y="196" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">index heartbeats</text> + </g> + + <rect x="40" y="250" width="760" height="110" rx="16" fill="var(--unomi-white)" stroke="var(--unomi-border)" stroke-width="2"/> + <text x="60" y="290" fill="var(--unomi-primary-dark)" font-size="14" font-weight="700">Persistence-based cluster nodes</text> + <text x="60" y="318" fill="var(--unomi-text)" font-size="12">ClusterNode docs in Elasticsearch / OpenSearch | heartbeats + stats | peer discovery via shared index</text> + <text x="60" y="342" fill="var(--unomi-text-muted)" font-size="12">UNOMI-877 | also powers cluster-aware task scheduler locks</text> + +</svg> diff --git a/src/main/webapp/assets/images/unomi3/request-tracing.svg b/src/main/webapp/assets/images/unomi3/request-tracing.svg new file mode 100644 index 0000000..66171b4 --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/request-tracing.svg @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 400" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Unomi request tracing: explain=true returns a requestTracing tree"> + <defs> +<style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + font-family: Inter, system-ui, sans-serif; + } + @keyframes dash-flow { to { stroke-dashoffset: -24; } } + @keyframes pulse-soft { 0%,100%{opacity:.55} 50%{opacity:.9} } + @keyframes float-y { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} } + @keyframes glow { 0%,100%{filter:drop-shadow(0 0 4px rgba(109,92,231,.35))} 50%{filter:drop-shadow(0 0 10px rgba(109,92,231,.55))} } + .flow { stroke-dasharray: 6 5; animation: dash-flow 1.6s linear infinite; } + .pulse { animation: pulse-soft 3s ease-in-out infinite; } + .float { animation: float-y 3.2s ease-in-out infinite; } + .glow { animation: glow 3s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .flow,.pulse,.float,.glow { animation: none !important; } + } +</style> + </defs> + <rect width="840" height="400" rx="20" fill="var(--unomi-bg-soft)"/> + <text x="40" y="40" fill="var(--unomi-text)" font-size="18" font-weight="700">Request tracing with explain=true</text> + <text x="40" y="62" fill="var(--unomi-text-muted)" font-size="13">Admin / tenant-admin only | tree of conditions, rules, and actions</text> + + <rect x="40" y="95" width="280" height="260" rx="16" fill="var(--unomi-white)" stroke="var(--unomi-border)" stroke-width="2"/> + <text x="60" y="128" fill="var(--unomi-primary-dark)" font-size="13" font-weight="700">Your request</text> + <text x="60" y="156" fill="var(--unomi-text)" font-size="12">POST /cxs/context.json</text> + <text x="60" y="178" fill="var(--unomi-text)" font-size="12">POST /cxs/eventcollector</text> + <text x="60" y="210" fill="var(--unomi-accent)" font-size="12" font-weight="700">?explain=true</text> + <text x="60" y="242" fill="var(--unomi-text-muted)" font-size="11">Auth: tenantId:privateKey</text> + <text x="60" y="266" fill="var(--unomi-text-muted)" font-size="11">(Basic) TENANT_ADMIN</text> + <text x="60" y="300" fill="var(--unomi-amber)" font-size="11" font-weight="600">Public key alone => 403</text> + <text x="60" y="324" fill="var(--unomi-text-muted)" font-size="11">UNOMI-873</text> + + <path d="M340 220 H400" stroke="var(--unomi-primary)" stroke-width="3" class="flow"/> + + <rect x="410" y="95" width="390" height="260" rx="16" fill="#0f172a"/> + <text x="430" y="128" fill="#94a3b8" font-size="12" font-weight="700">response.requestTracing</text> + <text x="430" y="160" fill="#34d399" font-size="12">event-collection</text> + <text x="450" y="186" fill="#a78bfa" font-size="11">condition-evaluation</text> + <text x="470" y="210" fill="#94a3b8" font-size="11">property | boolean</text> + <text x="450" y="240" fill="#a78bfa" font-size="11">action-execution</text> + <text x="470" y="264" fill="#94a3b8" font-size="11">increment-property</text> + <text x="470" y="288" fill="#94a3b8" font-size="11">evaluate-segments</text> + <text x="430" y="322" fill="#64748b" font-size="11">startTime / endTime / traces[]</text> + +</svg> diff --git a/src/main/webapp/assets/images/unomi3/shell-commands.svg b/src/main/webapp/assets/images/unomi3/shell-commands.svg new file mode 100644 index 0000000..54f6e9f --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/shell-commands.svg @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 400" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Unomi 3 Karaf shell: unomi:crud, tenant context, cache, and task commands"> + <defs><style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-accent: #00cec9; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + font-family: Inter, system-ui, sans-serif; + } + @keyframes dash-flow { to { stroke-dashoffset: -24; } } + @keyframes float-y { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} } + @keyframes glow { 0%,100%{filter:drop-shadow(0 0 4px rgba(109,92,231,.35))} 50%{filter:drop-shadow(0 0 10px rgba(109,92,231,.55))} } + .flow { stroke-dasharray: 6 5; animation: dash-flow 1.6s linear infinite; } + .float { animation: float-y 3.2s ease-in-out infinite; } + .glow { animation: glow 3s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .flow,.float,.glow { animation: none !important; } + } +</style></defs> + <rect width="840" height="400" rx="20" fill="var(--unomi-bg-soft)"/> + <text x="40" y="40" fill="var(--unomi-text)" font-size="18" font-weight="700">Karaf shell: unomi:*</text> + <text x="40" y="62" fill="var(--unomi-text-muted)" font-size="13">Unified CRUD, tenant session, cache ops, and task scheduler commands</text> + + <rect x="40" y="95" width="250" height="70" rx="14" fill="var(--unomi-primary)" class="glow"/> + <text x="165" y="125" text-anchor="middle" fill="#fff" font-size="15" font-weight="700">ssh -p 8102</text> + <text x="165" y="148" text-anchor="middle" fill="rgba(255,255,255,.85)" font-size="11">karaf@localhost</text> + + <path d="M300 130 H360" stroke="var(--unomi-primary)" stroke-width="3" class="flow"/> + + <g class="float"> + <rect x="370" y="90" width="200" height="100" rx="14" fill="var(--unomi-info-bg)" stroke="var(--unomi-info)" stroke-width="2"/> + <text x="470" y="125" text-anchor="middle" fill="var(--unomi-info)" font-size="13" font-weight="700">unomi:crud</text> + <text x="470" y="150" text-anchor="middle" fill="var(--unomi-text)" font-size="11">create read update</text> + <text x="470" y="172" text-anchor="middle" fill="var(--unomi-text)" font-size="11">delete list help</text> + </g> + + <g class="float" style="animation-delay:.25s"> + <rect x="590" y="90" width="210" height="100" rx="14" fill="var(--unomi-success-bg)" stroke="var(--unomi-success)" stroke-width="2"/> + <text x="695" y="125" text-anchor="middle" fill="var(--unomi-success)" font-size="13" font-weight="700">tenant-get / set</text> + <text x="695" y="150" text-anchor="middle" fill="var(--unomi-text)" font-size="11">session tenant context</text> + <text x="695" y="172" text-anchor="middle" fill="var(--unomi-text)" font-size="11">for CRUD + cache</text> + </g> + + <g class="float" style="animation-delay:.5s"> + <rect x="40" y="230" width="250" height="130" rx="14" fill="var(--unomi-amber-bg)" stroke="var(--unomi-amber)" stroke-width="2"/> + <text x="165" y="270" text-anchor="middle" fill="var(--unomi-amber)" font-size="13" font-weight="700">unomi:cache</text> + <text x="165" y="298" text-anchor="middle" fill="var(--unomi-text)" font-size="11">stats | clear | inspect</text> + <text x="165" y="322" text-anchor="middle" fill="var(--unomi-text)" font-size="11">view | remove | watch</text> + <text x="165" y="346" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">per tenant / type</text> + </g> + + <g class="float" style="animation-delay:.75s"> + <rect x="310" y="230" width="250" height="130" rx="14" fill="var(--unomi-primary-light)" stroke="var(--unomi-primary)" stroke-width="2"/> + <text x="435" y="270" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="13" font-weight="700">unomi:task-*</text> + <text x="435" y="298" text-anchor="middle" fill="var(--unomi-text)" font-size="11">list show cancel</text> + <text x="435" y="322" text-anchor="middle" fill="var(--unomi-text)" font-size="11">retry purge executor</text> + <text x="435" y="346" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">UNOMI-879 / 880</text> + </g> + + <g class="float" style="animation-delay:1s"> + <rect x="580" y="230" width="220" height="130" rx="14" fill="#0f172a"/> + <text x="690" y="270" text-anchor="middle" fill="#94a3b8" font-size="12" font-weight="700">Also</text> + <text x="690" y="298" text-anchor="middle" fill="#34d399" font-size="11">list-invalid-objects</text> + <text x="690" y="322" text-anchor="middle" fill="#a78bfa" font-size="11">rule-tail | event-tail</text> + <text x="690" y="346" text-anchor="middle" fill="#94a3b8" font-size="11">lifecycle | deploy</text> + </g> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/task-scheduler.svg b/src/main/webapp/assets/images/unomi3/task-scheduler.svg new file mode 100644 index 0000000..4d0c4b5 --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/task-scheduler.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 400" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Unomi 3.1 cluster-aware task scheduler with REST and shell"> + <defs> +<style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + font-family: Inter, system-ui, sans-serif; + } + @keyframes dash-flow { to { stroke-dashoffset: -24; } } + @keyframes pulse-soft { 0%,100%{opacity:.55} 50%{opacity:.9} } + @keyframes float-y { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} } + @keyframes glow { 0%,100%{filter:drop-shadow(0 0 4px rgba(109,92,231,.35))} 50%{filter:drop-shadow(0 0 10px rgba(109,92,231,.55))} } + .flow { stroke-dasharray: 6 5; animation: dash-flow 1.6s linear infinite; } + .pulse { animation: pulse-soft 3s ease-in-out infinite; } + .float { animation: float-y 3.2s ease-in-out infinite; } + .glow { animation: glow 3s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .flow,.pulse,.float,.glow { animation: none !important; } + } +</style> + </defs> + <rect width="840" height="400" rx="20" fill="var(--unomi-bg-soft)"/> + <text x="40" y="40" fill="var(--unomi-text)" font-size="18" font-weight="700">Cluster-aware task scheduler</text> + <text x="40" y="62" fill="var(--unomi-text-muted)" font-size="13">Durable tasks, locks, retry and resume across Unomi nodes</text> + + <rect x="300" y="95" width="240" height="70" rx="14" fill="var(--unomi-primary)" class="glow"/> + <text x="420" y="125" text-anchor="middle" fill="#fff" font-size="15" font-weight="700">SchedulerService</text> + <text x="420" y="148" text-anchor="middle" fill="rgba(255,255,255,.85)" font-size="11">persisted tasks + locks</text> + + <g class="float"> + <rect x="40" y="200" width="230" height="150" rx="14" fill="var(--unomi-info-bg)" stroke="var(--unomi-info)" stroke-width="2"/> + <text x="155" y="235" text-anchor="middle" fill="var(--unomi-info)" font-size="13" font-weight="700">REST /cxs/tasks</text> + <text x="155" y="262" text-anchor="middle" fill="var(--unomi-text)" font-size="11">list | show | cancel</text> + <text x="155" y="286" text-anchor="middle" fill="var(--unomi-text)" font-size="11">retry | resume</text> + <text x="155" y="318" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">admin / tenant-admin</text> + </g> + <g class="float" style="animation-delay:.3s"> + <rect x="305" y="200" width="230" height="150" rx="14" fill="var(--unomi-amber-bg)" stroke="var(--unomi-amber)" stroke-width="2"/> + <text x="420" y="235" text-anchor="middle" fill="var(--unomi-amber)" font-size="13" font-weight="700">Built-in types</text> + <text x="420" y="262" text-anchor="middle" fill="var(--unomi-text)" font-size="11">profile-purge</text> + <text x="420" y="286" text-anchor="middle" fill="var(--unomi-text)" font-size="11">segment-date-recalculation</text> + <text x="420" y="310" text-anchor="middle" fill="var(--unomi-text)" font-size="11">rules-statistics-refresh</text> + <text x="420" y="334" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">cache-refresh-* | task-purge</text> + </g> + <g class="float" style="animation-delay:.6s"> + <rect x="570" y="200" width="230" height="150" rx="14" fill="var(--unomi-success-bg)" stroke="var(--unomi-success)" stroke-width="2"/> + <text x="685" y="235" text-anchor="middle" fill="var(--unomi-success)" font-size="13" font-weight="700">Karaf shell</text> + <text x="685" y="262" text-anchor="middle" fill="var(--unomi-text)" font-size="11">unomi:task-list</text> + <text x="685" y="286" text-anchor="middle" fill="var(--unomi-text)" font-size="11">task-show | task-retry</text> + <text x="685" y="310" text-anchor="middle" fill="var(--unomi-text)" font-size="11">task-cancel | task-purge</text> + <text x="685" y="334" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">task-executor</text> + </g> + +</svg> diff --git a/src/main/webapp/assets/images/unomi3/unit-harness-vs-it.svg b/src/main/webapp/assets/images/unomi3/unit-harness-vs-it.svg index 043fdcd..6c4b19f 100644 --- a/src/main/webapp/assets/images/unomi3/unit-harness-vs-it.svg +++ b/src/main/webapp/assets/images/unomi3/unit-harness-vs-it.svg @@ -45,26 +45,26 @@ <!-- Unit track --> <g> <rect x="40" y="90" width="360" height="210" rx="18" fill="var(--unomi-success-bg)" stroke="var(--unomi-success)" stroke-width="2"/> - <text x="60" y="122" fill="var(--unomi-success)" font-size="14" font-weight="700">UNIT · seconds</text> + <text x="60" y="122" fill="var(--unomi-success)" font-size="14" font-weight="700">UNIT | seconds</text> <rect x="60" y="140" width="320" height="44" rx="12" fill="var(--unomi-white)" class="glow"/> <text x="220" y="168" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="600">InMemoryPersistenceServiceImpl</text> - <text x="80" y="210" fill="var(--unomi-text)" font-size="12">→ TestHelper wires real *ServiceImpl</text> - <text x="80" y="232" fill="var(--unomi-text)" font-size="12">→ conditions · refresh simulation</text> - <text x="80" y="254" fill="var(--unomi-text)" font-size="12">→ no Docker · no Karaf boot</text> - <text x="80" y="280" fill="var(--unomi-success)" font-size="12" font-weight="600">Independent · high-signal</text> + <text x="80" y="210" fill="var(--unomi-text)" font-size="12">-> TestHelper wires real *ServiceImpl</text> + <text x="80" y="232" fill="var(--unomi-text)" font-size="12">-> conditions | refresh simulation</text> + <text x="80" y="254" fill="var(--unomi-text)" font-size="12">-> no Docker | no Karaf boot</text> + <text x="80" y="280" fill="var(--unomi-success)" font-size="12" font-weight="600">Independent | high-signal</text> </g> <!-- IT track --> <g> <rect x="440" y="90" width="360" height="210" rx="18" fill="var(--unomi-info-bg)" stroke="var(--unomi-info)" stroke-width="2"/> - <text x="460" y="122" fill="var(--unomi-info)" font-size="14" font-weight="700">INTEGRATION · minutes+</text> + <text x="460" y="122" fill="var(--unomi-info)" font-size="14" font-weight="700">INTEGRATION | minutes+</text> <rect x="460" y="140" width="140" height="44" rx="12" fill="var(--unomi-white)" class="float"/> <text x="530" y="168" text-anchor="middle" fill="var(--unomi-text)" font-size="12" font-weight="600">Karaf</text> <rect x="620" y="140" width="160" height="44" rx="12" fill="var(--unomi-white)" class="float" style="animation-delay:.4s"/> <text x="700" y="168" text-anchor="middle" fill="var(--unomi-text)" font-size="12" font-weight="600">ES / OpenSearch</text> - <text x="480" y="210" fill="var(--unomi-text)" font-size="12">→ Pax Exam + Docker stack</text> - <text x="480" y="232" fill="var(--unomi-text)" font-size="12">→ full REST · clustering · tenants</text> - <text x="480" y="254" fill="var(--unomi-text)" font-size="12">→ proves the live platform</text> + <text x="480" y="210" fill="var(--unomi-text)" font-size="12">-> Pax Exam + Docker stack</text> + <text x="480" y="232" fill="var(--unomi-text)" font-size="12">-> full REST | clustering | tenants</text> + <text x="480" y="254" fill="var(--unomi-text)" font-size="12">-> proves the live platform</text> <text x="480" y="280" fill="var(--unomi-info)" font-size="12" font-weight="600">End-to-end confidence</text> </g> </svg> diff --git a/src/main/webapp/blog/index.html b/src/main/webapp/blog/index.html index 174b9f7..c83f6e5 100644 --- a/src/main/webapp/blog/index.html +++ b/src/main/webapp/blog/index.html @@ -111,6 +111,210 @@ layout: default </div> </section> +<!-- DX: build.sh --> +<section class="section section-alt"> + <div class="container"> + <div class="row align-items-center g-4 g-lg-5"> + <div class="col-lg-6"> + <span class="badge badge-primary mb-3">Developer experience</span> + <h2 class="h3 fw-bold mb-3"> + <a href="/blog/unomi-3-build-sh.html" class="text-decoration-none text-body">One entry point: build.sh</a> + </h2> + <p class="text-muted mb-4">Preflight, Maven build, integration tests, and deploy — one script locally and on GitHub Actions, with a <code>build.ps1</code> twin for Windows.</p> + <a href="/blog/unomi-3-build-sh.html" class="btn btn-primary"> + Read the build.sh post <i class="bi bi-arrow-right ms-1"></i> + </a> + </div> + <div class="col-lg-6"> + <a href="/blog/unomi-3-build-sh.html" class="d-block rounded-3 overflow-hidden shadow-sm" aria-label="Read build.sh post"> + <img src="/assets/images/unomi3/build-sh.svg" class="img-fluid w-100" alt="build.sh hub for preflight build test deploy" loading="lazy"> + </a> + </div> + </div> + </div> +</section> + +<!-- Docs: Manual --> +<section class="section"> + <div class="container"> + <div class="row align-items-center g-4 g-lg-5"> + <div class="col-lg-6 order-lg-2"> + <span class="badge badge-primary mb-3">Documentation</span> + <h2 class="h3 fw-bold mb-3"> + <a href="/blog/unomi-3-manual.html" class="text-decoration-none text-body">Unomi 3 manual improvements</a> + </h2> + <p class="text-muted mb-4">PlantUML diagrams in AsciiDoc, an Unomi-branded HTML/PDF theme, and deep chapters for migrations, multi-tenancy, upgrades, and conditions.</p> + <a href="/blog/unomi-3-manual.html" class="btn btn-primary"> + Read the manual post <i class="bi bi-arrow-right ms-1"></i> + </a> + </div> + <div class="col-lg-6 order-lg-1"> + <a href="/blog/unomi-3-manual.html" class="d-block rounded-3 overflow-hidden shadow-sm" aria-label="Read Unomi 3 manual improvements"> + <img src="/assets/images/unomi3/manual-plantuml-theme.svg" class="img-fluid w-100" alt="PlantUML source to branded Unomi manual" loading="lazy"> + </a> + </div> + </div> + </div> +</section> + +<!-- Quality: IT execution --> +<section class="section section-alt"> + <div class="container"> + <div class="row align-items-center g-4 g-lg-5"> + <div class="col-lg-6"> + <span class="badge badge-primary mb-3">Quality · Developer experience</span> + <h2 class="h3 fw-bold mb-3"> + <a href="/blog/unomi-3-it-execution.html" class="text-decoration-none text-body">Clearer integration test runs</a> + </h2> + <p class="text-muted mb-4">Live progress bars with ETA, quieter Maven/Docker CI output, and tooling to archive and compare long IT suites — before / after from real GitHub Actions runs.</p> + <a href="/blog/unomi-3-it-execution.html" class="btn btn-primary"> + Read the IT execution post <i class="bi bi-arrow-right ms-1"></i> + </a> + </div> + <div class="col-lg-6"> + <a href="/blog/unomi-3-it-execution.html" class="d-block rounded-3 overflow-hidden shadow-sm" aria-label="Read clearer integration test runs"> + <img src="/assets/images/unomi3/it-progress-before-after.svg" class="img-fluid w-100" alt="IT console before and after progress bars" loading="lazy"> + </a> + </div> + </div> + </div> +</section> + +<!-- Quality: unit harness --> +<section class="section"> + <div class="container"> + <div class="row align-items-center g-4 g-lg-5"> + <div class="col-lg-6 order-lg-2"> + <span class="badge badge-primary mb-3">Quality · Unit tests</span> + <h2 class="h3 fw-bold mb-3"> + <a href="/blog/unomi-3-unit-testing-harness.html" class="text-decoration-none text-body">Faster service unit tests</a> + </h2> + <p class="text-muted mb-4"><code>InMemoryPersistenceServiceImpl</code> — a full PersistenceService on the test classpath so Profile, Event, Rules, and Segment logic runs in seconds without Karaf.</p> + <a href="/blog/unomi-3-unit-testing-harness.html" class="btn btn-primary"> + Read the unit harness post <i class="bi bi-arrow-right ms-1"></i> + </a> + </div> + <div class="col-lg-6 order-lg-1"> + <a href="/blog/unomi-3-unit-testing-harness.html" class="d-block rounded-3 overflow-hidden shadow-sm" aria-label="Read unit testing harness"> + <img src="/assets/images/unomi3/unit-harness-vs-it.svg" class="img-fluid w-100" alt="Unit harness versus integration tests" loading="lazy"> + </a> + </div> + </div> + </div> +</section> + +<!-- Platform + OpenSearch pair --> +<section class="section section-alt"> + <div class="container"> + <div class="row justify-content-center text-center mb-4"> + <div class="col-lg-7"> + <p class="section-label">Platform & ops</p> + <h2 class="h4 mb-0">Foundation, search, shell, scheduler, tracing</h2> + </div> + </div> + <div class="row g-4"> + <div class="col-md-6 col-lg-3"> + <article class="feature-card h-100 overflow-hidden p-0 position-relative"> + <a href="/blog/unomi-3-platform-foundation.html" class="d-block" aria-label="Read platform foundation"> + <img src="/assets/images/unomi3/platform-foundation.svg" class="img-fluid w-100" alt="Unomi 3 platform foundation" loading="lazy"> + </a> + <div class="p-3"> + <span class="badge badge-primary mb-2">Platform</span> + <h3 class="h6 fw-semibold mb-2"> + <a href="/blog/unomi-3-platform-foundation.html" class="text-decoration-none text-body">Platform foundation</a> + </h3> + <p class="small text-muted mb-0">Java 17, Karaf 4.4, ES9, persistence clustering.</p> + <a href="/blog/unomi-3-platform-foundation.html" class="stretched-link" aria-label="Read platform foundation"></a> + </div> + </article> + </div> + <div class="col-md-6 col-lg-3"> + <article class="feature-card h-100 overflow-hidden p-0 position-relative"> + <a href="/blog/unomi-3-opensearch.html" class="d-block" aria-label="Read OpenSearch post"> + <img src="/assets/images/unomi3/opensearch-dual.svg" class="img-fluid w-100" alt="Elasticsearch and OpenSearch" loading="lazy"> + </a> + <div class="p-3"> + <span class="badge badge-primary mb-2">Ops</span> + <h3 class="h6 fw-semibold mb-2"> + <a href="/blog/unomi-3-opensearch.html" class="text-decoration-none text-body">ES9 & OpenSearch</a> + </h3> + <p class="small text-muted mb-0">Dual backends, same SPI, migrate guide.</p> + <a href="/blog/unomi-3-opensearch.html" class="stretched-link" aria-label="Read OpenSearch post"></a> + </div> + </article> + </div> + <div class="col-md-6 col-lg-3"> + <article class="feature-card h-100 overflow-hidden p-0 position-relative"> + <a href="/blog/unomi-3-shell-commands.html" class="d-block" aria-label="Read shell commands"> + <img src="/assets/images/unomi3/shell-commands.svg" class="img-fluid w-100" alt="Unomi Karaf shell commands" loading="lazy"> + </a> + <div class="p-3"> + <span class="badge badge-primary mb-2">Ops</span> + <h3 class="h6 fw-semibold mb-2"> + <a href="/blog/unomi-3-shell-commands.html" class="text-decoration-none text-body">Shell commands</a> + </h3> + <p class="small text-muted mb-0"><code>unomi:crud</code>, tenant, cache, tasks.</p> + <a href="/blog/unomi-3-shell-commands.html" class="stretched-link" aria-label="Read shell commands"></a> + </div> + </article> + </div> + <div class="col-md-6 col-lg-3"> + <article class="feature-card h-100 overflow-hidden p-0 position-relative"> + <a href="/blog/unomi-3-task-scheduler.html" class="d-block" aria-label="Read task scheduler"> + <img src="/assets/images/unomi3/task-scheduler.svg" class="img-fluid w-100" alt="Task scheduler" loading="lazy"> + </a> + <div class="p-3"> + <span class="badge badge-primary mb-2">3.1</span> + <h3 class="h6 fw-semibold mb-2"> + <a href="/blog/unomi-3-task-scheduler.html" class="text-decoration-none text-body">Task scheduler</a> + </h3> + <p class="small text-muted mb-0">REST <code>/cxs/tasks</code>, shell, cluster locks.</p> + <a href="/blog/unomi-3-task-scheduler.html" class="stretched-link" aria-label="Read task scheduler"></a> + </div> + </article> + </div> + <div class="col-md-6 col-lg-3"> + <article class="feature-card h-100 overflow-hidden p-0 position-relative"> + <a href="/blog/unomi-3-request-tracing.html" class="d-block" aria-label="Read request tracing"> + <img src="/assets/images/unomi3/request-tracing.svg" class="img-fluid w-100" alt="Request tracing explain" loading="lazy"> + </a> + <div class="p-3"> + <span class="badge badge-primary mb-2">Debug</span> + <h3 class="h6 fw-semibold mb-2"> + <a href="/blog/unomi-3-request-tracing.html" class="text-decoration-none text-body">Request tracing</a> + </h3> + <p class="small text-muted mb-0"><code>explain=true</code> with verified curl examples.</p> + <a href="/blog/unomi-3-request-tracing.html" class="stretched-link" aria-label="Read request tracing"></a> + </div> + </article> + </div> + </div> + </div> +</section> + +<!-- OpenAPI enrichment --> +<section class="section"> + <div class="container"> + <div class="row align-items-center g-4 g-lg-5"> + <div class="col-lg-6"> + <span class="badge badge-primary mb-3">Unomi 3.1 · REST</span> + <h2 class="h3 fw-bold mb-3"> + <a href="/blog/unomi-3-openapi-enrichment.html" class="text-decoration-none text-body">Richer OpenAPI from Javadoc</a> + </h2> + <p class="text-muted mb-4">Unomi 3.1 merges endpoint Javadoc into <code>/cxs/openapi.json</code> — summaries, statuses, examples, and schema notes for humans and agents (UNOMI-963).</p> + <a href="/blog/unomi-3-openapi-enrichment.html" class="btn btn-primary"> + Read the OpenAPI post <i class="bi bi-arrow-right ms-1"></i> + </a> + </div> + <div class="col-lg-6"> + <a href="/blog/unomi-3-openapi-enrichment.html" class="d-block rounded-3 overflow-hidden shadow-sm" aria-label="Read OpenAPI enrichment"> + <img src="/assets/images/unomi3/openapi-enrichment.svg" class="img-fluid w-100" alt="OpenAPI enrichment pipeline" loading="lazy"> + </a> + </div> + </div> + </div> +</section> + <!-- Welcome + topics --> <section class="section"> <div class="container"> @@ -140,6 +344,16 @@ layout: default <a href="/blog/unomi-3-multitenancy.html" class="badge badge-primary text-decoration-none px-3 py-2">Multi-tenancy</a> <a href="/blog/unomi-3-ai-agents.html" class="badge badge-primary text-decoration-none px-3 py-2">AI agents</a> <a href="/blog/migrating-to-unomi-3.html" class="badge badge-primary text-decoration-none px-3 py-2">Migration</a> + <a href="/blog/unomi-3-it-execution.html" class="badge badge-primary text-decoration-none px-3 py-2">IT execution</a> + <a href="/blog/unomi-3-manual.html" class="badge badge-primary text-decoration-none px-3 py-2">Manual</a> + <a href="/blog/unomi-3-build-sh.html" class="badge badge-primary text-decoration-none px-3 py-2">build.sh</a> + <a href="/blog/unomi-3-unit-testing-harness.html" class="badge badge-primary text-decoration-none px-3 py-2">Unit harness</a> + <a href="/blog/unomi-3-platform-foundation.html" class="badge badge-primary text-decoration-none px-3 py-2">Platform</a> + <a href="/blog/unomi-3-opensearch.html" class="badge badge-primary text-decoration-none px-3 py-2">OpenSearch</a> + <a href="/blog/unomi-3-shell-commands.html" class="badge badge-primary text-decoration-none px-3 py-2">Shell</a> + <a href="/blog/unomi-3-task-scheduler.html" class="badge badge-primary text-decoration-none px-3 py-2">Scheduler</a> + <a href="/blog/unomi-3-request-tracing.html" class="badge badge-primary text-decoration-none px-3 py-2">Tracing</a> + <a href="/blog/unomi-3-openapi-enrichment.html" class="badge badge-primary text-decoration-none px-3 py-2">OpenAPI</a> <a href="/use-cases.html" class="badge badge-secondary text-decoration-none px-3 py-2">Use cases</a> <a href="/community/events/index.html" class="badge badge-secondary text-decoration-none px-3 py-2">Events</a> </div> @@ -151,7 +365,7 @@ layout: default </section> <!-- Reading order --> -<section class="section section-alt"> +<section class="section"> <div class="container"> <div class="row justify-content-center text-center mb-4"> <div class="col-lg-7"> @@ -191,6 +405,15 @@ layout: default </div> <p class="text-center small text-muted mt-4 mb-0"> Then: <a href="/blog/migrating-to-unomi-3.html">Migrating from 2.x</a> · + <a href="/blog/unomi-3-platform-foundation.html">Platform</a> · + <a href="/blog/unomi-3-opensearch.html">OpenSearch</a> · + <a href="/blog/unomi-3-shell-commands.html">Shell</a> · + <a href="/blog/unomi-3-unit-testing-harness.html">Unit harness</a> · + <a href="/blog/unomi-3-request-tracing.html">Tracing</a> · + <a href="/blog/unomi-3-openapi-enrichment.html">OpenAPI</a> · + <a href="/blog/unomi-3-build-sh.html">build.sh</a> · + <a href="/blog/unomi-3-manual.html">Manual</a> · + <a href="/blog/unomi-3-it-execution.html">IT execution</a> · <a href="/download.html">Download</a> · <a href="/get-started.html">Get started</a> </p> diff --git a/src/main/webapp/blog/unomi-3-build-sh.html b/src/main/webapp/blog/unomi-3-build-sh.html new file mode 100644 index 0000000..e06759e --- /dev/null +++ b/src/main/webapp/blog/unomi-3-build-sh.html @@ -0,0 +1,79 @@ +--- +title: One entry point — Unomi 3 build.sh +description: "How Apache Unomi 3 replaced scattered build scripts with a single build.sh — preflight checks, CI mode, integration tests, deploy, and Windows build.ps1 parity." +keywords: "Unomi build.sh, Apache Unomi developer experience, UNOMI-887, Maven CI, OpenSearch integration tests" +layout: default +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3 · Developer experience</p> + <h1>One entry point: <code>build.sh</code></h1> + <p class="lead">Preflight, build, test, and deploy — the same script locally and on GitHub Actions.</p> + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>Before Unomi 3, contributors juggled several ad-hoc scripts (<code>buildAndRun.sh</code>, <code>compileDeploy.sh</code>, and friends). + <a href="https://issues.apache.org/jira/browse/UNOMI-887">UNOMI-887</a> + / + <a href="https://github.com/apache/unomi/pull/737">#737</a> + folded those workflows into a single + <strong><code>./build.sh</code></strong> + at the repository root — with clearer errors, colorized output, and flags that match how people actually work.</p> + + <img src="/assets/images/unomi3/build-sh.svg" class="img-fluid my-4" alt="build.sh as one hub for preflight, build, test, and deploy"/> + + <h2 class="h4 mt-4 mb-3">What it replaced</h2> + <p>Legacy one-off scripts for “build and run,” “build without tests,” packaging, and deploy were removed in favor of one maintained entry point. + The PowerShell twin + <code>build.ps1</code> + tracks the same flags for Windows contributors.</p> + + <h2 class="h4 mt-5 mb-3">What you get</h2> + <ul> + <li><strong>Preflight</strong> — Java, Maven, GraphViz; memory/disk heuristics; ports for debug sessions; fail early with readable messages</li> + <li><strong>Build flexibility</strong> — skip tests, OpenSearch vs Elasticsearch, named distributions, offline / Maven debug, optional Javadoc gate</li> + <li><strong>Integration tests</strong> — <code>--integration-tests</code>, <code>--single-test</code>, IT debug/suspend, keep container, stream search logs when needed, memory sampler</li> + <li><strong>Run & deploy</strong> — <code>--deploy --karaf-home</code>, Karaf debug ports, <code>--auto-start</code> for search engines, <code>--no-karaf</code> for package-only builds</li> + <li><strong>CI mode</strong> — <code>--ci</code> is non-interactive, batch Maven (<code>-B -ntp</code>), no Karaf side-trips; GitHub Actions calls the same script as you do locally (<a href="https://github.com/apache/unomi/pull/764">#764</a>)</li> + <li><strong>UX details</strong> — section headers, traps with line/context on failure, <code>NO_COLOR</code> respected, optional <code>--log-file</code> tee</li> + </ul> + + <h2 class="h4 mt-5 mb-3">Try it</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>./build.sh --help + +# Default install +./build.sh + +# Match GitHub Actions shape +./build.sh --ci + +# Full IT matrix flavors +./build.sh --integration-tests +./build.sh --integration-tests --use-opensearch + +# One flaky test under a debugger +./build.sh --integration-tests \ + --single-test org.apache.unomi.itests.graphql.GraphQLEventIT \ + --it-debug --it-debug-suspend + +# Deploy KAR into an existing Karaf +./build.sh --deploy --karaf-home ~/apache-karaf</code></pre> + + <h2 class="h4 mt-5 mb-3">Why it matters</h2> + <p>Contributor friction kills review velocity. One documented command that validates the environment, builds the right distribution, and runs the same gates as CI means less “works on my machine” archaeology — and pairs with the + <a href="/blog/unomi-3-it-execution.html">clearer IT console</a> + and + <a href="/blog/unomi-3-manual.html">manual</a> + investments in Unomi 3.</p> + + <p class="mt-4"> + <a class="btn btn-primary" href="https://github.com/apache/unomi/blob/master/build.sh">View build.sh on GitHub</a> + <a class="btn btn-outline-primary ms-2" href="{{ site.data.unomi.manual.published_latest }}#_using_the_build_script">Manual: using the build script</a> + <a class="btn btn-outline-primary ms-2" href="/contribute/testing.html">Contribute · Testing</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/blog/unomi-3-it-execution.html b/src/main/webapp/blog/unomi-3-it-execution.html new file mode 100644 index 0000000..e620d92 --- /dev/null +++ b/src/main/webapp/blog/unomi-3-it-execution.html @@ -0,0 +1,96 @@ +--- +title: Clearer integration test runs in Unomi 3 +description: "How Unomi 3 improved IT execution — live progress bars with ETA, quieter Maven and Docker output, and tooling for diagnosing long CI suites." +keywords: "Unomi 3 integration tests, ProgressListener, CI logs, Apache Unomi IT, build.sh" +layout: default +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3 · Quality</p> + <h1>Clearer integration test runs in Unomi 3</h1> + <p class="lead">Progress bars, quieter CI output, and tools that turn a 90–minute suite into something you can actually follow.</p> + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>Apache Unomi’s integration tests are deliberately heavy: Pax Exam boots a real Karaf, Docker starts Elasticsearch or OpenSearch, and Failsafe walks a large end-to-end suite. That work takes on the order of + <strong>one to two hours</strong> on + <a href="https://github.com/apache/unomi/actions/workflows/unomi-ci-build-tests.yml">GitHub Actions</a> + (for example + <a href="https://github.com/apache/unomi/actions/runs/29200354118">run #29200354118</a> + on <code>master</code>, ~1h 52m wall time). For years the console mostly said “still downloading” or “still running” — with almost no sense of + <em>how far</em> + or + <em>what was left</em>.</p> + <p>Unomi 3 invested in the + <strong>execution experience</strong> + of that suite: live progress, quieter logs, and developer tooling around archives and memory. The goal is not fewer tests — it is being able to watch (and debug) the ones we already run.</p> + + <img src="/assets/images/unomi3/it-progress-before-after.svg" class="img-fluid my-4" alt="Before and after IT console: Maven download spam versus progress bar with ETA"/> + + <h2 class="h4 mt-4 mb-3">What changed</h2> + <ul> + <li><strong>Live progress</strong> — <a href="https://issues.apache.org/jira/browse/UNOMI-912">UNOMI-912</a> / <a href="https://github.com/apache/unomi/pull/735">#735</a> added <code>ProgressSuite</code> + <code>ProgressListener</code>: accurate total, percentage, ETA, pass/fail counters, and a CSV of the slowest tests at the end.</li> + <li><strong>Quieter Maven in CI</strong> — <a href="https://github.com/apache/unomi/pull/764">#764</a> aligned Actions with <code>./build.sh --ci</code>; follow-up work forces <code>-B -ntp</code> so transfer “Progress (1): … kB” lines stop drowning the log.</li> + <li><strong>Docker search logs off the console</strong> — Elasticsearch/OpenSearch container logs go to files under <code>itests/target/</code> by default; opt in with <code>--search-engine-logs</code> when you need them live.</li> + <li><strong>Diagnose, don’t re-run blindly</strong> — <a href="https://github.com/apache/unomi/pull/770">#770</a> (archive / compare / <code>kt.sh</code>) and <a href="https://github.com/apache/unomi/pull/782">#782</a> (memory sampling + CI artifacts) make flaky vs systematic failures easier to classify.</li> + </ul> + + <h2 class="h4 mt-5 mb-3">Before / after (CI console)</h2> + <p>Raw job log downloads from Actions require repository privileges, so the excerpts below are + <strong>representative reconstructions</strong> + of what contributors and committers see in the “Execute integration tests” job — anchored to real public runs and the UNOMI-912 acceptance output.</p> + + <p class="small text-muted mb-2"><strong>Before</strong> — typical shape of an older IT job before progress-aware running and transfer quieting (see e.g. + <a href="https://github.com/apache/unomi/actions/runs/19324846828">run #19324846828</a>, + 2025-11-13, pre–UNOMI-912 merge). Walls of Maven transfer progress, little suite-level signal:</p> + <pre class="p-3 rounded-3 small overflow-auto" style="max-height: 14rem;"><code>Downloading from central: https://repo.maven.apache.org/.../junit-4.13.2.jar +Progress (1): 4.1/384 kB +Progress (1): 48/384 kB +Progress (1): 128/384 kB +Downloaded from central: .../junit-4.13.2.jar (0.4 s) +Downloading from central: https://repo.maven.apache.org/.../hamcrest-core-1.3.jar +Progress (1): 7.2/45 kB +... +[INFO] --- docker-maven-plugin: start (elasticsearch) +... thousands of similar lines ... +# Suite still running - no % complete, no ETA, no live fail list</code></pre> + + <p class="small text-muted mb-2 mt-4"><strong>After</strong> — same class of job on Unomi 3 tip (e.g. + <a href="https://github.com/apache/unomi/actions/runs/29200354118">run #29200354118</a>, + 2026-07-12). Transfer UI suppressed; <code>ProgressListener</code> owns the “are we stuck?” question. Format from + <a href="https://issues.apache.org/jira/browse/UNOMI-912">UNOMI-912</a>:</p> + <pre class="p-3 rounded-3 small overflow-auto" style="max-height: 14rem;"><code>[#######-----------------------] Progress: 23.33% (35/150 tests). + Estimated time remaining: 4m 12s. Successful: 35, Failed: 0 + +# ... later ... + +Top 10 Slowest Tests: +Rank,Test Name,Duration (ms) +1,"GraphQLProfileIT.testUpdateProperty",18234 +2,"ProfileMergeIT.testMerge",14925 +...</code></pre> + + <h2 class="h4 mt-5 mb-3">Try it locally</h2> + <pre class="code-block-light p-3 rounded-3 small"><code>./build.sh --integration-tests +# CI-shaped (batch + no transfer progress): +./build.sh --ci --integration-tests</code></pre> + <p>After a run, see <code>itests/README.md</code> for <code>archive-it-run.sh</code>, <code>compare-it-runs.sh</code>, <code>kt.sh</code>, and memory sampling. On Actions, download the <code>it-memory-metrics-*</code> artifact from a completed IT job.</p> + + <h2 class="h4 mt-5 mb-3">How this fits the Unomi 3 quality story</h2> + <p>Progress and cleaner logs make the + <em>long</em> + path usable. Unomi 3 also added a + <em>short</em> + path — the in-memory persistence harness for fast service unit tests without Karaf or Docker. Different layers, same idea: confidence without guessing.</p> + + <p class="mt-4"> + <a class="btn btn-primary" href="https://github.com/apache/unomi/tree/master/itests">Browse itests on GitHub</a> + <a class="btn btn-outline-primary ms-2" href="/blog/introducing-apache-unomi-3.html">Unomi 3 intro</a> + <a class="btn btn-outline-primary ms-2" href="/contribute/testing.html">Contribute · Testing</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/blog/unomi-3-manual.html b/src/main/webapp/blog/unomi-3-manual.html new file mode 100644 index 0000000..cebe055 --- /dev/null +++ b/src/main/webapp/blog/unomi-3-manual.html @@ -0,0 +1,102 @@ +--- +title: Unomi 3 manual — PlantUML, branded theme, deeper docs +description: "How the Apache Unomi 3 manual gained PlantUML/GraphViz diagrams, an Unomi-branded HTML/PDF theme, and major content expansions for migrations, multi-tenancy, and platform upgrades." +keywords: "Apache Unomi manual, PlantUML AsciiDoc, Unomi 3 documentation, AsciiDoctor diagrams, Unomi CSS theme" +layout: default +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3 · Documentation</p> + <h1>Unomi 3 manual: diagrams, theme, and depth</h1> + <p class="lead">PlantUML in AsciiDoc, a brand-aligned HTML/PDF look, and chapters that finally match the Unomi 3 platform.</p> + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>Shipping Unomi 3 without a manual that looks and reads like Unomi 3 would leave operators guessing. Over several + <a href="https://issues.apache.org/jira/browse/UNOMI-882">UNOMI-882</a> + and + <a href="https://issues.apache.org/jira/browse/UNOMI-960">UNOMI-960</a> + efforts, the project rebuilt how the official manual is + <em>authored</em>, + <em>rendered</em>, and + <em>navigated</em> + — from diagram toolchain to CSS theme to whole new chapters.</p> + + <img src="/assets/images/unomi3/manual-plantuml-theme.svg" class="img-fluid my-4" alt="AsciiDoc PlantUML source builds into a branded Unomi 3 manual with diagrams"/> + + <h2 class="h4 mt-4 mb-3">PlantUML (and GraphViz) in the build</h2> + <p><a href="https://github.com/apache/unomi/pull/753">#753</a> wired + <strong>AsciiDoctor Diagram</strong> + into <code>manual/pom.xml</code>: <code>asciidoctorj-diagram</code> + PlantUML, with GraphViz available via + <code>GRAPHVIZ_DOT</code> / <code>graphvizdot</code>. CI installs GraphViz so HTML and PDF builds stay reproducible.</p> + <p>Diagrams live + <strong>in the AsciiDoc</strong> + as <code>[plantuml]</code> blocks — reviewable in pull requests, regenerated on every docs build, no orphaned PNGs in git. + Tip today has on the order of + <strong>62 PlantUML diagrams</strong> + across ~24 chapters (architecture C4, sequences, upgrade flows, condition evaluation, scheduler, past-event strategies, and more).</p> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>[plantuml] +---- +@startuml +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml +Container(unomi, "Apache Unomi") +Component(restApi, "REST API", "JAX-RS") +@enduml +----</code></pre> + <p class="small text-muted">Local tip: install GraphViz (<code>brew install graphviz</code> / <code>apt-get install graphviz</code>), then + <code>cd manual && mvn -ntp clean install</code>. + <code>build.sh</code> also checks for <code>dot</code>.</p> + + <h2 class="h4 mt-5 mb-3">Branded HTML & PDF theme</h2> + <p>A later slice of + <a href="https://github.com/apache/unomi/pull/821">#821</a> + (UNOMI-960) replaced the default AsciiDoctor chrome with an + <strong>Unomi-branded</strong> + stylesheet that tracks + <a href="https://unomi.apache.org/">unomi.apache.org</a>:</p> + <ul> + <li><strong>Design tokens</strong> — <code>--unomi-primary</code> (<code>#6d5ce7</code>), accent purple, Inter typography</li> + <li><strong>Site nav & footer</strong> via docinfo, version badge, hero cover for the HTML book</li> + <li><strong>Responsive UX</strong> — burger nav + bottom TOC sheet under 1024px; desktop sidebar TOC from 1024px</li> + <li><strong>PDF</strong> — dedicated <code>unomi-theme.yml</code> under <code>manual/src/theme/</code> with matching brand assets</li> + </ul> + <p>Reading the published manual should feel continuous with the project website, not like a separate default book skin.</p> + + <h2 class="h4 mt-5 mb-3">Content that matches Unomi 3</h2> + <p><a href="https://github.com/apache/unomi/pull/783">#783</a> synced the book to + <strong>Apache Unomi 3.x</strong> + and expanded (or heavily refreshed) material that operators actually need:</p> + <ul> + <li><strong>Platform story</strong> — architecture, security, configuration, clustering, data structures</li> + <li><strong>3.1 product</strong> — multi-tenancy, task scheduler, OpenSearch notes, condition validation / evaluation, property types</li> + <li><strong>Migrations</strong> — 2.x→3.0, 3.0→3.1, ES7→ES9, Elasticsearch→OpenSearch, V2 compatibility mode, script authoring</li> + <li><strong>Platform upgrades</strong> — dedicated <code>upgrades/</code> runbooks (Java, Karaf, Elasticsearch, Log4j, CXF/Jackson/Jetty, dependency hygiene, ITs)</li> + <li><strong>Day-to-day APIs</strong> — request examples (including explain/tracing), GraphQL, shell commands, JavaScript tracker guide, recipes</li> + <li><strong>Reference depth</strong> — conditions guide and builtin type catalogs (UNOMI-960), past-event conditions, profile tracking</li> + </ul> + <p>“What’s new” for 3.1 now points readers straight into these chapters instead of leaving gaps between release notes and the old 2.x-shaped manual.</p> + + <h2 class="h4 mt-5 mb-3">How to add a diagram</h2> + <ol> + <li>Edit the relevant <code>.adoc</code> under <code>manual/src/main/asciidoc/</code></li> + <li>Add a <code>[plantuml]</code> (or GraphViz) block next to the prose it illustrates</li> + <li>Build the manual module (GraphViz required for some diagrams)</li> + <li>Review the generated PNG in the PR like any other source change</li> + </ol> + <p>Website promotional diagrams stay animated SVG / Mermaid on + <a href="/blog/">unomi.apache.org</a>; + the + <strong>manual</strong> + sticks to PlantUML→PNG so AsciiDoctor HTML/PDF stay self-contained.</p> + + <p class="mt-4"> + <a class="btn btn-primary" href="{{ site.data.unomi.manual.published_latest }}">Open the latest manual</a> + <a class="btn btn-outline-primary ms-2" href="{{ site.data.unomi.manual.whats_new }}">What’s new in 3.1</a> + <a class="btn btn-outline-primary ms-2" href="https://github.com/apache/unomi/tree/master/manual/src/main/asciidoc">Browse AsciiDoc on GitHub</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/blog/unomi-3-openapi-enrichment.html b/src/main/webapp/blog/unomi-3-openapi-enrichment.html new file mode 100644 index 0000000..e400418 --- /dev/null +++ b/src/main/webapp/blog/unomi-3-openapi-enrichment.html @@ -0,0 +1,70 @@ +--- +title: Richer OpenAPI docs in Unomi 3.1 +description: "How Unomi 3.1 enriches /cxs/openapi.json from JAX-RS Javadoc — summaries, statuses, examples, and schemas without duplicating Swagger annotations (UNOMI-963)." +keywords: "Unomi OpenAPI, openapi.json, UNOMI-963, CXF OpenAPI, Javadoc enrichment, Unomi 3.1 REST" +layout: default +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3.1 · Developer experience</p> + <h1>Richer OpenAPI docs from Javadoc</h1> + <p class="lead">Unomi 3.1 ships an enriched <code>/cxs/openapi.json</code> — summaries, status codes, examples, and schema notes merged from endpoint Javadoc.</p> + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>Stock CXF OpenAPI generation is a great skeleton and a thin reading experience: operation ids, parameter names, and “default response.” + Unomi 3.1 ( + <a href="https://issues.apache.org/jira/browse/UNOMI-963">UNOMI-963</a>) + keeps a <strong>single source of truth in Javadoc</strong> and merges richer documentation into the live OpenAPI document at runtime.</p> + + <img src="/assets/images/unomi3/openapi-enrichment.svg" class="img-fluid my-4" alt="Javadoc to enrichment pack to CXF openapi.json pipeline"/> + + <h2 class="h4 mt-4 mb-3">How the pipeline works</h2> + <ol> + <li><strong>Build time</strong> — a Maven Mojo scans JAX-RS sources and writes an enrichment pack (<code>openapi-doc.json</code>) into each REST module JAR</li> + <li><strong>Runtime</strong> — Unomi’s CXF OpenAPI customizer loads every pack on the classpath and merges summaries, parameters, named statuses, examples, and schema field docs into <code>/cxs/openapi.json</code></li> + </ol> + <p>Authors document once on the Java method / type; agents, Swagger UI, and humans all see the same result.</p> + + <h2 class="h4 mt-5 mb-3">Capabilities</h2> + <ul> + <li><strong>Build-time Mojo</strong> — scan JAX-RS sources; emit enrichment pack JSON into each REST module JAR</li> + <li><strong>Multi-pack merge</strong> — runtime loader merges packs from core <code>rest</code> and extension modules</li> + <li><strong>CXF OpenApiFeature customizer</strong> — merges into live <code>/cxs/openapi.json</code> (and Swagger UI)</li> + <li><strong>Operation summary / description</strong> — from the first Javadoc sentences</li> + <li><strong>Parameter docs</strong> — <code>@param</code> Java names remapped to <code>@PathParam</code> / <code>@QueryParam</code> values</li> + <li><strong>Return / success docs</strong> — from <code>@return</code></li> + <li><strong>Named HTTP statuses</strong> — <code>@api.status</code> for realistic 2xx / 4xx / 5xx (not only “default response”)</li> + <li><strong>Examples</strong> — <code>@api.example</code> on operations and on schema properties</li> + <li><strong>Schema field docs</strong> — Javadoc on DTO fields (enums as string + values; maps document expected keys)</li> + <li><strong>Request / response models</strong> — enrichment for types used by hot paths (tenants, context, profiles, rules, segments, privacy, extensions, …)</li> + <li><strong>extraSourceRoots</strong> — document DTOs that live in a sibling <code>api</code> module</li> + <li><strong>Servlet endpoints</strong> — optional enrichment for documented <code>HttpServlet</code> paths (class-level tags)</li> + <li><strong>Single source of truth</strong> — no duplicate Swagger annotation tax on every method</li> + </ul> + + <h2 class="h4 mt-5 mb-3">Try it on a running 3.1 server</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>curl -u karaf:karaf http://localhost:8181/cxs/openapi.json | jq '.paths["/tenants/{tenantId}"].get'</code></pre> + <p>Compare operation descriptions, response maps, and examples against a 2.x or early-3.0 baseline — the inventory stays stable; the documentation density is what changes.</p> + + <h2 class="h4 mt-5 mb-3">Why publish this with 3.1</h2> + <p>Unomi 3 kept the REST surface familiar for migrations. Enrichment makes that surface + <em>discoverable</em> + for contributors, customer engineers, and LLM / agent tooling that already prefer OpenAPI over tribal knowledge. + It pairs with the + <a href="/blog/unomi-3-manual.html">manual</a> + and + <a href="/blog/unomi-3-request-tracing.html">request tracing</a> + investments: docs you can read, traces you can run, schemas you can generate clients from.</p> + + <p class="mt-4"> + <a class="btn btn-primary" href="{{ site.data.unomi.manual.published_latest }}">Unomi 3.1 manual</a> + <a class="btn btn-outline-primary ms-2" href="/blog/migrating-to-unomi-3.html">API stability / migrate</a> + <a class="btn btn-outline-primary ms-2" href="https://issues.apache.org/jira/browse/UNOMI-963">UNOMI-963</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/blog/unomi-3-opensearch.html b/src/main/webapp/blog/unomi-3-opensearch.html new file mode 100644 index 0000000..02db882 --- /dev/null +++ b/src/main/webapp/blog/unomi-3-opensearch.html @@ -0,0 +1,75 @@ +--- +title: Elasticsearch 9 and OpenSearch in Unomi 3 +description: "Unomi 3 runs on Elasticsearch 9 by default and ships a first-class OpenSearch persistence distribution — same SPI, migrate guide, and build flags." +keywords: "Unomi OpenSearch, Elasticsearch 9, Apache Unomi persistence, UNOMI-828, migrate Elasticsearch to OpenSearch" +layout: default +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3 · Operations</p> + <h1>Elasticsearch 9 and OpenSearch in Unomi 3</h1> + <p class="lead">One CDP runtime, two supported search backends — pick the distribution that matches your ops constraints.</p> + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>Unomi 3 modernized the search tier on both sides of the fork. + Elasticsearch moved to the <strong>9.x Java API client</strong> (the legacy High Level REST Client path is gone), and + <a href="https://issues.apache.org/jira/browse/UNOMI-828">UNOMI-828</a> + / + <a href="https://github.com/apache/unomi/pull/715">#715</a> + made <strong>OpenSearch a first-class persistence module</strong> with its own distribution packaging.</p> + + <img src="/assets/images/unomi3/opensearch-dual.svg" class="img-fluid my-4" alt="Elasticsearch 9 and OpenSearch dual backends sharing Unomi PersistenceService SPI"/> + + <h2 class="h4 mt-4 mb-3">Capabilities</h2> + <ul> + <li><strong>Dual distributions</strong> — <code>unomi-distribution-elasticsearch</code> and <code>unomi-distribution-opensearch</code></li> + <li><strong>Shared PersistenceService SPI</strong> — profiles, events, segments, rules, clustering, and the task scheduler stay on the same application paths</li> + <li><strong>Elasticsearch 9 Java API client</strong> — HLRC retired; config prefix <code>org.apache.unomi.elasticsearch.*</code></li> + <li><strong>OpenSearch module</strong> — <code>persistence-opensearch</code>; config prefix <code>org.apache.unomi.opensearch.*</code> (addresses, cluster name, credentials, SSL, index prefix)</li> + <li><strong>Build flags</strong> — Maven / <code>build.sh --use-opensearch</code>; IT matrix can run either engine</li> + <li><strong>Docker / env</strong> — <code>UNOMI_DISTRIBUTION=...</code> selects packaging</li> + <li><strong>Migration guide</strong> — documented ES → OpenSearch path (index naming, clients, verification)</li> + <li><strong>Version pins in 3.1</strong> — Elasticsearch 9.4.x line and OpenSearch 3.7.x companion pins in the reactor</li> + </ul> + + <h2 class="h4 mt-4 mb-3">What you choose</h2> + <ul> + <li><strong>Elasticsearch</strong> — <code>persistence-elasticsearch</code>, config prefix <code>org.apache.unomi.elasticsearch.*</code> (addresses, cluster name, credentials, SSL, index prefix)</li> + <li><strong>OpenSearch</strong> — <code>persistence-opensearch</code>, config prefix <code>org.apache.unomi.opensearch.*</code> with the same conceptual shape</li> + </ul> + <p>Both implement the Unomi <code>PersistenceService</code> SPI, so profiles, events, segments, rules, clustering, and the task scheduler stay on the same application paths.</p> + + <h2 class="h4 mt-5 mb-3">Build and run flavors</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code># Maven / build.sh +./build.sh +./build.sh --use-opensearch +./build.sh --integration-tests --use-opensearch + +# Docker distribution hint +UNOMI_DISTRIBUTION=unomi-distribution-elasticsearch +UNOMI_DISTRIBUTION=unomi-distribution-opensearch</code></pre> + + <h2 class="h4 mt-5 mb-3">Migrating engines</h2> + <p>Moving an existing deployment from Elasticsearch to OpenSearch is documented as its own migration path (index naming, client settings, verification). + Start from + <a href="{{ site.data.unomi.manual.es_to_os }}">Migrate from Elasticsearch to OpenSearch</a> + in the Unomi 3 manual.</p> + + <h2 class="h4 mt-5 mb-3">Why it matters</h2> + <p>Regulated and cloud-restricted environments often need an OpenSearch footprint; others stay on Elasticsearch. + Unomi 3 stops treating that as a fork of the product — it is a packaging and configuration choice on a shared SPI, alongside the + <a href="/blog/unomi-3-platform-foundation.html">platform foundation</a> + upgrade (Java 17, Karaf 4.4, persistence clustering).</p> + + <p class="mt-4"> + <a class="btn btn-primary" href="{{ site.data.unomi.manual.es_to_os }}">Manual: ES to OpenSearch</a> + <a class="btn btn-outline-primary ms-2" href="{{ site.data.unomi.manual.published_latest }}#_configuration">Manual: configuration</a> + <a class="btn btn-outline-primary ms-2" href="/blog/unomi-3-build-sh.html">build.sh</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/blog/unomi-3-platform-foundation.html b/src/main/webapp/blog/unomi-3-platform-foundation.html new file mode 100644 index 0000000..cc67d10 --- /dev/null +++ b/src/main/webapp/blog/unomi-3-platform-foundation.html @@ -0,0 +1,50 @@ +--- +title: Unomi 3 platform foundation +description: "What Unomi 3.0 changed under the hood — Java 17, Karaf 4.4, Elasticsearch 9, and persistence-based clustering without Cellar or Hazelcast." +keywords: "Unomi 3 platform, Java 17, Karaf 4.4, UNOMI-877, persistence clustering, Elasticsearch 9" +layout: default +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3 · Platform</p> + <h1>Unomi 3 platform foundation</h1> + <p class="lead">The 3.0 runtime floor — Java 17, Karaf 4.4, Elasticsearch 9, and clustering without Cellar or Hazelcast.</p> + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>Before multi-tenancy and the Unomi 3.1 product leap could stick, the project needed a boring, modern runtime. + That is what the 3.0 line delivered: fewer legacies, clearer installers, and a cluster model that matches how Unomi already stores data.</p> + + <img src="/assets/images/unomi3/platform-foundation.svg" class="img-fluid my-4" alt="Unomi 3 platform tiles: Java 17, Karaf 4.4, search clients, persistence clustering"/> + + <h2 class="h4 mt-4 mb-3">Baseline upgrades</h2> + <ul> + <li><strong>Java 17+</strong> — Java 11 is retired for Unomi 3 installers and CI</li> + <li><strong>Apache Karaf 4.4</strong> — continued into 3.1 with current patch lines (for example 4.4.11)</li> + <li><strong>Elasticsearch 9</strong> — Java API client; HLRC retired; OpenSearch as a peer distribution (see + <a href="/blog/unomi-3-opensearch.html">Elasticsearch 9 and OpenSearch</a>)</li> + <li><strong>Log / dependency pins</strong> — platform bump PRs keep Log4j and client stacks on supported lines</li> + </ul> + + <h2 class="h4 mt-5 mb-3">Persistence-based clustering (UNOMI-877)</h2> + <p>Cellar and Hazelcast are gone. Nodes register <code>ClusterNode</code> documents in Elasticsearch or OpenSearch, publish heartbeats and stats, and discover peers through the shared index. + The same mechanism underpins executor selection and locking for the + <a href="/blog/unomi-3-task-scheduler.html">cluster-aware task scheduler</a>.</p> + + <h2 class="h4 mt-5 mb-3">What operators should do</h2> + <p>Treat 3.0 as the platform cutover: confirm Java 17 hosts, pick an ES9 or OpenSearch distribution, and read the clustering and health-check chapters before stacking tenants and tasks. + The + <a href="/blog/migrating-to-unomi-3.html">migrating from 2.x</a> + post covers the REST and auth cutover; the manual holds the day-two runbooks.</p> + + <p class="mt-4"> + <a class="btn btn-primary" href="{{ site.data.unomi.manual.published_latest }}#_clustering">Manual: clustering</a> + <a class="btn btn-outline-primary ms-2" href="{{ site.data.unomi.manual.whats_new }}">What’s new</a> + <a class="btn btn-outline-primary ms-2" href="/blog/introducing-apache-unomi-3.html">Introducing Unomi 3</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/blog/unomi-3-request-tracing.html b/src/main/webapp/blog/unomi-3-request-tracing.html new file mode 100644 index 0000000..f5bfdf8 --- /dev/null +++ b/src/main/webapp/blog/unomi-3-request-tracing.html @@ -0,0 +1,168 @@ +--- +title: Request tracing with explain=true in Unomi 3 +description: "Debug context and eventcollector requests with explain=true — admin-gated requestTracing trees, verified curl examples, and what 403 means for public API keys." +keywords: "Unomi explain, requestTracing, Unomi tracing, UNOMI-873, eventcollector explain, context.json explain" +layout: default +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3 · Debugging</p> + <h1>Request tracing with <code>explain=true</code></h1> + <p class="lead">See condition evaluations, rule actions, and timings on <code>/cxs/context.json</code> and <code>/cxs/eventcollector</code>.</p> + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>When a rule “should have fired” or a session update looks wrong, guessing from logs is slow. + Unomi 3 adds query-parameter tracing ( + <a href="https://issues.apache.org/jira/browse/UNOMI-873">UNOMI-873</a> + / + <a href="https://github.com/apache/unomi/pull/775">#775</a>): + append <code>explain=true</code> and the response includes a <code>requestTracing</code> tree.</p> + + <img src="/assets/images/unomi3/request-tracing.svg" class="img-fluid my-4" alt="explain=true producing a requestTracing tree of conditions and actions"/> + + <h2 class="h4 mt-4 mb-3">Where it works</h2> + <ul> + <li><code>POST</code>/<code>GET</code> <code>/cxs/context.json</code> and <code>/cxs/context.js</code></li> + <li><code>POST</code>/<code>GET</code> <code>/cxs/eventcollector</code></li> + </ul> + + <h2 class="h4 mt-5 mb-3">Capabilities</h2> + <ul> + <li><strong>Opt-in per request</strong> — <code>?explain=true</code> enables tracing for that call only</li> + <li><strong>Response field</strong> — <code>requestTracing</code> on <code>ContextResponse</code> / <code>EventCollectorResponse</code></li> + <li><strong>Hierarchical tree</strong> — nested <code>TraceNode</code> children for nested work</li> + <li><strong>Per-node fields</strong> — <code>operationType</code>, <code>description</code>, <code>context</code>, <code>result</code>, <code>startTime</code>, <code>endTime</code>, <code>traces[]</code>, <code>children[]</code></li> + <li><strong>What gets traced</strong> — request processing, event validation, rule evaluation, condition evaluation (property/boolean/…), action execution (visit counters, segment eval, age, …), personalization / content-filter evaluation on context requests</li> + <li><strong>Timings</strong> — wall-clock start/end on every node for latency hunting</li> + <li><strong>Auth gate</strong> — <code>ROLE_UNOMI_ADMIN</code> / <code>ROLE_UNOMI_TENANT_ADMIN</code> only; public API key alone returns HTTP 403</li> + <li><strong>Low overhead when off</strong> — tracer disabled unless <code>explain=true</code>; reset after the request</li> + <li><strong>Programmatic API</strong> — <code>TracerService</code> / <code>RequestTracer</code> for services that start nested operations</li> + <li><strong>IT coverage</strong> — authorized explain success + unauthorized 403 paths</li> + </ul> + + <h2 class="h4 mt-5 mb-3">Auth: tenant admin, not the public key</h2> + <p>Tracing is restricted to <strong>ADMINISTRATOR</strong> / <strong>TENANT_ADMINISTRATOR</strong>. + In practice that means authenticate with the tenant <strong>private</strong> API key (Basic <code>tenantId:privateApiKey</code>), not the public track key.</p> + <p>We verified the following against a local <strong>Unomi 3.1.0-SNAPSHOT</strong> (HTTPS). Public-key + <code>explain=true</code> returned HTTP <strong>403</strong> with:</p> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>{"errorMessage":"Insufficient privileges to access tracing information"}</code></pre> + <p>Integration coverage: <code>EventsCollectorIT#testEventsCollectorWithExplain</code> and <code>#testEventsCollectorWithExplainUnauthorized</code>.</p> + + <h2 class="h4 mt-5 mb-3">Example: event collector</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>curl -X POST 'http://localhost:8181/cxs/eventcollector?explain=true' \ + -u 'YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY' \ + -H 'Content-Type: application/json' \ + -d @- <<'EOF' +{ + "sessionId": "trace-demo-1", + "events": [ + { + "eventType": "view", + "scope": "myscope", + "source": { + "itemType": "site", + "scope": "myscope", + "itemId": "mysite" + }, + "target": { + "itemType": "page", + "scope": "myscope", + "itemId": "homepage" + } + } + ] +} +EOF</code></pre> + <p>Verified excerpt (trimmed) from a live 3.1 response — session create, condition checks, visit counters:</p> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>{ + "updated": 6, + "requestTracing": { + "operationType": "event-collection", + "description": "Event collection request processed successfully", + "startTime": 1784099217601, + "endTime": 1784099217629, + "traces": [ + "Sending event: sessionCreated - Context: …", + "Processing event for profile: …" + ], + "children": [ + { + "operationType": "condition-evaluation", + "description": "Condition evaluation completed", + "result": "true", + "children": [ + { + "operationType": "boolean", + "description": "OR condition succeeded on subcondition", + "result": "true" + } + ] + }, + { + "operationType": "action-execution", + "description": "Action execution completed", + "children": [ + { + "operationType": "increment-property", + "description": "Property incremented successfully", + "traces": [ + "Processing property increment - Context: {propertyName=nbOfVisits, …}", + "Property increment result - Context: {newValue=1, isUpdated=true}" + ] + } + ] + } + ] + } +}</code></pre> + + <h2 class="h4 mt-5 mb-3">Example: context.json</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>curl -X POST \ + 'http://localhost:8181/cxs/context.json?sessionId=trace-demo-1&explain=true' \ + -u 'YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY' \ + -H 'Content-Type: application/json' \ + -d @- <<'EOF' +{ + "source": { + "itemId": "homepage", + "itemType": "page", + "scope": "myscope" + }, + "requiredProfileProperties": ["*"], + "requireSegments": true +} +EOF</code></pre> + <p>Live trace root on the same stack:</p> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>"requestTracing": { + "operationType": "context-request", + "description": "Context request processed successfully", + "children": [ + { "operationType": "condition-evaluation", … }, + { "operationType": "action-execution", + "children": [ + { "operationType": "evaluate-age", "description": "No changes needed" }, + { "operationType": "evaluate-segments", "description": "No changes needed" } + ] + } + ] +}</code></pre> + + <h2 class="h4 mt-5 mb-3">How to read the tree</h2> + <ul> + <li><strong>Timings</strong> — subtract <code>endTime - startTime</code> on hot nodes when hunting latency</li> + <li><strong>Conditions</strong> — look for <code>result: false</code> on rules you expected to match</li> + <li><strong>Actions</strong> — <code>increment-property</code>, <code>evaluate-segments</code>, visit helpers show whether profile state actually changed</li> + </ul> + <p>For save-time condition checks (before runtime), use condition validation in the manual; tracing is the runtime companion.</p> + + <p class="mt-4"> + <a class="btn btn-primary" href="{{ site.data.unomi.manual.published_latest }}#_request_tracing_explain">Manual: explain / tracing</a> + <a class="btn btn-outline-primary ms-2" href="/blog/unomi-3-manual.html">Manual improvements</a> + <a class="btn btn-outline-primary ms-2" href="/blog/unomi-3-task-scheduler.html">Task scheduler</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/blog/unomi-3-shell-commands.html b/src/main/webapp/blog/unomi-3-shell-commands.html new file mode 100644 index 0000000..1e1b6e8 --- /dev/null +++ b/src/main/webapp/blog/unomi-3-shell-commands.html @@ -0,0 +1,116 @@ +--- +title: Unomi 3 Karaf shell commands +description: "Unomi 3 expands the unomi:* Karaf shell — unified unomi:crud, tenant session context, cache ops, task scheduler commands, and list-invalid-objects (UNOMI-879 / 880)." +keywords: "Unomi shell, unomi:crud, Karaf shell, unomi:cache, unomi:task-list, tenant-set, UNOMI-879, UNOMI-880" +layout: default +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3 · Operator experience</p> + <h1>Karaf shell commands in Unomi 3</h1> + <p class="lead"><code>unomi:crud</code>, tenant session context, cache tools, and task commands — operate without leaving SSH.</p> + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>The Unomi Karaf shell (<code>ssh -p 8102 karaf@localhost</code>, namespace <code>unomi:</code>) has always been the fastest way to inspect rules, tail events, and manage lifecycle. + Unomi 3 ( + <a href="https://issues.apache.org/jira/browse/UNOMI-879">UNOMI-879</a> + / + <a href="https://issues.apache.org/jira/browse/UNOMI-880">UNOMI-880</a> + / + <a href="https://github.com/apache/unomi/pull/755">#755</a> + / + <a href="https://github.com/apache/unomi/pull/763">#763</a>) + widens that toolkit for multi-tenancy, caches, and the cluster-aware scheduler — with a unified CRUD surface instead of one-off list/view/remove commands for every type.</p> + + <img src="/assets/images/unomi3/shell-commands.svg" class="img-fluid my-4" alt="Unomi 3 shell: crud, tenant, cache, and task command families"/> + + <h2 class="h4 mt-4 mb-3">Capabilities at a glance</h2> + <ul> + <li><strong>Unified <code>unomi:crud</code></strong> — <code>create</code> / <code>read</code> / <code>update</code> / <code>delete</code> / <code>list</code> / <code>help</code> for many object types</li> + <li><strong>JSON or file input</strong> — inline JSON, <code>file://</code> paths, or positional id + body patterns</li> + <li><strong>Tabular + CSV lists</strong> — ASCII tables with <code>--csv</code> and <code>-n</code> / <code>--max-entries</code></li> + <li><strong>Completers</strong> — operation, type, and id completion for faster operator workflows</li> + <li><strong>Tenant session</strong> — <code>unomi:tenant-get</code> / <code>unomi:tenant-set</code> so following commands run in a tenant (or <code>system</code>)</li> + <li><strong>Tenant & API key CRUD</strong> — create/list/update tenants and API keys from the shell</li> + <li><strong>Cache management</strong> — <code>unomi:cache</code> stats, reset, clear, inspect, view, remove, watch, filters by tenant/type, CSV</li> + <li><strong>Scheduler ops</strong> — <code>unomi:task-list|show|cancel|retry|purge|executor</code> (see + <a href="/blog/unomi-3-task-scheduler.html">task scheduler</a>)</li> + <li><strong>Validation assist</strong> — <code>unomi:list-invalid-objects</code> for rules/segments/goals/campaigns/scoring with unresolved types</li> + <li><strong>Classic live debugging</strong> — <code>rule-tail</code>, <code>rule-watch</code>, <code>event-tail</code>, <code>rule-reset-stats</code></li> + <li><strong>Lifecycle</strong> — <code>setup</code>, <code>migrate</code>, <code>start</code>, <code>stop</code>, <code>version</code></li> + <li><strong>Definition deploy</strong> — <code>deploy-definition</code> / <code>undeploy-definition</code> with bundle/type wildcards</li> + <li><strong>Extension types</strong> — e.g. JSON Schema via <code>unomi:crud ... schema</code> when the extension is installed</li> + </ul> + + <h2 class="h4 mt-5 mb-3">Connect and discover</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>ssh -p 8102 karaf@localhost +# default user/pass: karaf/karaf (change etc/users.properties ASAP) + +unomi:<TAB> # list commands +help unomi:crud +help unomi:cache</code></pre> + + <h2 class="h4 mt-5 mb-3">Tenant context first</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>unomi:tenant-set acme +unomi:tenant-get +# subsequent CRUD / cache commands use this tenant</code></pre> + + <h2 class="h4 mt-5 mb-3">Unified CRUD</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>unomi:crud help rule +unomi:crud list rule --csv -n 50 +unomi:crud read rule my-rule-id +unomi:crud create rule --file /tmp/rule.json +unomi:crud update rule my-rule-id -d='{"itemId":"my-rule-id","enabled":false}' +unomi:crud delete rule my-rule-id + +unomi:crud list tenant +unomi:crud create apikey -d='{"tenantId":"acme","keyType":"PRIVATE"}' +unomi:crud list profile -n 20 +unomi:crud list schema # when json-schema extension is present</code></pre> + + <h3 class="h6 mt-4 mb-2">Object types registered for <code>unomi:crud</code></h3> + <p class="small text-muted"><code>actiontype</code>, <code>apikey</code>, <code>campaign</code>, <code>campaignevent</code>, <code>conditiontype</code>, <code>consent</code>, <code>event</code>, <code>goal</code>, <code>persona</code>, <code>profile</code>, <code>profilealias</code>, <code>propertytype</code>, <code>rule</code>, <code>rulestats</code>, <code>scope</code>, <code>scoring</code>, <code>segment</code>, <code>session</code>, <code>tenant</code>, <code>topic</code>, pl [...] + + <h2 class="h4 mt-5 mb-3">Cache</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>unomi:cache --stats +unomi:cache --stats --detailed --tenant acme +unomi:cache --stats --type profile --csv +unomi:cache --clear --tenant acme +unomi:cache --inspect +unomi:cache --view --id ENTRY_ID --type … --tenant acme +unomi:cache --remove --id ENTRY_ID --type … --tenant acme +unomi:cache --watch 5</code></pre> + + <h2 class="h4 mt-5 mb-3">Scheduler (shell twin of REST)</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>unomi:task-list -s RUNNING +unomi:task-list -t profile-purge --limit 20 +unomi:task-show TASK_ID +unomi:task-retry TASK_ID --reset +unomi:task-cancel TASK_ID +unomi:task-purge --days 14 --force +unomi:task-executor</code></pre> + + <h2 class="h4 mt-5 mb-3">Still there: live debugging & lifecycle</h2> + <ul> + <li><code>rule-list|view|remove|reset-stats|tail|watch</code></li> + <li><code>event-tail|view|list|search</code></li> + <li><code>action-list|view</code>, <code>condition-list|view</code></li> + <li><code>profile-list|view|remove</code>, <code>segment-*</code>, <code>session-*</code></li> + <li><code>list-invalid-objects</code></li> + <li><code>deploy-definition</code> / <code>undeploy-definition</code></li> + <li><code>setup</code>, <code>migrate</code>, <code>start</code>, <code>stop</code>, <code>version</code></li> + </ul> + <p>Many day-to-day inspect/edit flows now go through <code>unomi:crud</code>; the dedicated list/view/remove commands remain useful for scripting habits and live tails.</p> + + <p class="mt-4"> + <a class="btn btn-primary" href="{{ site.data.unomi.manual.published_latest }}#_shell_commands">Manual: shell commands</a> + <a class="btn btn-outline-primary ms-2" href="/blog/unomi-3-task-scheduler.html">Task scheduler</a> + <a class="btn btn-outline-primary ms-2" href="/blog/unomi-3-multitenancy.html">Multi-tenancy</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/blog/unomi-3-task-scheduler.html b/src/main/webapp/blog/unomi-3-task-scheduler.html new file mode 100644 index 0000000..47be3ac --- /dev/null +++ b/src/main/webapp/blog/unomi-3-task-scheduler.html @@ -0,0 +1,102 @@ +--- +title: Cluster-aware task scheduler in Unomi 3.1 +description: "Unomi 3.1 durable task scheduler — full capability list: persistence, cluster locks, lifecycle, fluent builder, REST ops, shell, metrics, resume, and built-in jobs." +keywords: "Unomi task scheduler, /cxs/tasks, unomi:task-list, Unomi 3.1, profile-purge, cluster tasks, TaskExecutor" +layout: default +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3.1 · Operations</p> + <h1>Cluster-aware task scheduler</h1> + <p class="lead">Durable background work with persistence, locks, retry/resume, REST, shell, and a plugin-friendly fluent API.</p> + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>Unomi 3.1 introduces a first-class <strong>task scheduler</strong> for work that must run reliably on a single node or across a cluster: + maintenance timers, request offload (for example profile-merge follow-up), and operator-visible retry/cancel.</p> + <p>It sits on the + <a href="/blog/unomi-3-platform-foundation.html">persistence-based clustering</a> + model — executor nodes and locks use the shared search index rather than Cellar or Hazelcast.</p> + + <img src="/assets/images/unomi3/task-scheduler.svg" class="img-fluid my-4" alt="SchedulerService with REST, built-in task types, and Karaf shell"/> + + <h2 class="h4 mt-4 mb-3">Capabilities at a glance</h2> + <ul> + <li><strong>Task types + executors</strong> — each logical <code>taskType</code> is handled by a registered <code>TaskExecutor</code> (core services or plugins)</li> + <li><strong>Persistent or in-memory</strong> — cluster-visible <code>scheduledTask</code> items in Elasticsearch/OpenSearch, or <code>nonPersistent()</code> node-local tasks (typical for cache refresh)</li> + <li><strong>One-shot and recurring</strong> — <code>asOneShot()</code>, or period + <code>withFixedDelay()</code> / <code>withFixedRate()</code>, with optional <code>withInitialDelay</code></li> + <li><strong>Cluster coordination</strong> — executor nodes (<code>scheduler.executorNode</code>), cluster locks, optional <code>runOnAllNodes()</code>, parallel execution control via <code>disallowParallelExecution()</code></li> + <li><strong>Lifecycle</strong> — statuses <code>SCHEDULED</code>, <code>WAITING</code>, <code>RUNNING</code>, <code>COMPLETED</code>, <code>FAILED</code>, <code>CANCELLED</code>, <code>CRASHED</code></li> + <li><strong>Retries</strong> — <code>withMaxRetries</code> / <code>withRetryDelay</code> (auto); manual retry via REST/shell (optional failure-count reset)</li> + <li><strong>Dependencies</strong> — <code>withDependencies(taskId…)</code> so a task waits until prior work is <code>COMPLETED</code></li> + <li><strong>Checkpoints / resume</strong> — long jobs save <code>checkpointData</code> / <code>currentStep</code>; operators resume <code>CRASHED</code> tasks when the executor implements <code>canResume</code> / <code>resume</code></li> + <li><strong>Parameters</strong> — JSON-serializable map passed into the executor (include <code>tenantId</code> and run under <code>executeAsTenant</code> for tenant isolation)</li> + <li><strong>System tasks</strong> — <code>asSystemTask()</code> for jobs created at init that should be preserved across restarts</li> + <li><strong>Fluent API</strong> — <code>schedulerService.newTask(type)</code> builder; also <code>createRecurringTask</code> / <code>createTask</code> helpers</li> + <li><strong>Plugin registration</strong> — register/unregister <code>TaskExecutor</code> from OSGi DS <code>activate</code> / <code>deactivate</code></li> + <li><strong>REST ops</strong> — list (filter by status/type, pagination), get, cancel, retry, resume (admin / tenant-admin); task <em>creation</em> stays in Java services/plugins</li> + <li><strong>Karaf shell</strong> — <code>unomi:task-list|show|cancel|retry|purge|executor</code></li> + <li><strong>Built-in purge</strong> — periodic <code>task-purge</code> of old <code>COMPLETED</code> records (<code>scheduler.purgeTaskEnabled</code>, TTL days)</li> + <li><strong>Metrics</strong> — <code>getMetric</code> / <code>getAllMetrics</code> (completed, failed, crashed, running, lock conflicts, recovery attempts, …)</li> + <li><strong>Config</strong> — thread pool size, stable <code>scheduler.nodeId</code>, lock timeout, executor vs API-only node roles in <code>org.apache.unomi.services.cfg</code></li> + </ul> + + <h2 class="h4 mt-5 mb-3">REST surface</h2> + <p>Base path <code>/cxs/tasks</code> (roles <code>ROLE_UNOMI_ADMIN</code> / <code>ROLE_UNOMI_TENANT_ADMIN</code>) — monitoring and operations, not create:</p> + <ul> + <li><code>GET /cxs/tasks</code> — paginated list; filters <code>status=</code>, <code>type=</code>, <code>offset</code>, <code>limit</code></li> + <li><code>GET /cxs/tasks/{id}</code> — detail (parameters, checkpoint, errors)</li> + <li><code>DELETE /cxs/tasks/{id}</code> — cancel (HTTP 204; record kept as <code>CANCELLED</code>)</li> + <li><code>POST /cxs/tasks/{id}/retry</code> — optional <code>?resetFailureCount=true</code></li> + <li><code>POST /cxs/tasks/{id}/resume</code> — for <code>CRASHED</code> + checkpoint-capable executors</li> + </ul> + + <h2 class="h4 mt-5 mb-3">Karaf shell</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>unomi:task-list [-s STATUS] [-t TYPE] [--limit N] +unomi:task-show <id> +unomi:task-cancel <id> +unomi:task-retry <id> [--reset] +unomi:task-purge [--days N] [--force] +unomi:task-executor # is this node an executor? nodeId?</code></pre> + + <h2 class="h4 mt-5 mb-3">Fluent scheduling (plugins / services)</h2> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>schedulerService.newTask("cache-refresh-segment") + .nonPersistent() + .withPeriod(1, TimeUnit.SECONDS) + .withFixedDelay() + .disallowParallelExecution() + .withSimpleExecutor(() -> refreshSegmentCache()) + .schedule(); + +schedulerService.newTask("export-tenant-snapshot") + .withParameters(Map.of("tenantId", "acme")) + .asOneShot() + .withMaxRetries(3) + .withRetryDelay(2, TimeUnit.MINUTES) + .disallowParallelExecution() + .withExecutor(myExportExecutor) + .schedule();</code></pre> + + <h2 class="h4 mt-5 mb-3">Built-in task types you will see</h2> + <ul> + <li><code>cache-refresh-*</code> — reload segments, rules, property types, scopes, … into memory</li> + <li><code>profile-purge</code></li> + <li><code>segment-date-recalculation</code></li> + <li><code>rules-statistics-refresh</code></li> + <li><code>task-purge</code> — purge old completed scheduler tasks</li> + <li><code>merge-profiles-reassign-data</code> — async session/event reassignment after profile merge (<code>baseplugin</code>)</li> + <li><code>clusterNodeStatisticsUpdate</code> / <code>clusterStaleNodesCleanup</code> — clustering maintenance</li> + </ul> + <p>Async enrichment-style jobs can reuse the same model instead of ad-hoc threads — register an executor, schedule with the builder, operate via REST/shell.</p> + + <p class="mt-4"> + <a class="btn btn-primary" href="{{ site.data.unomi.manual.published_latest }}#_scheduler">Manual: scheduler</a> + <a class="btn btn-outline-primary ms-2" href="/blog/unomi-3-platform-foundation.html">Platform foundation</a> + <a class="btn btn-outline-primary ms-2" href="/blog/unomi-3-request-tracing.html">Request tracing</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/blog/unomi-3-unit-testing-harness.html b/src/main/webapp/blog/unomi-3-unit-testing-harness.html new file mode 100644 index 0000000..9e4135b --- /dev/null +++ b/src/main/webapp/blog/unomi-3-unit-testing-harness.html @@ -0,0 +1,84 @@ +--- +title: Faster Unomi service unit tests — InMemoryPersistenceServiceImpl +description: "How Unomi 3’s in-memory PersistenceService test harness lets Profile, Event, Rules, and Segment services run real unit tests in seconds without Karaf or Elasticsearch." +keywords: "Unomi unit tests, InMemoryPersistenceServiceImpl, UNOMI-881, Unomi TestHelper, Surefire" +layout: default +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3 · Quality</p> + <h1>Faster service unit tests</h1> + <p class="lead"><code>InMemoryPersistenceServiceImpl</code> — a full PersistenceService on the test classpath so service logic no longer waits on Karaf.</p> + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>In Unomi 2.x, most confidence meant <strong>integration tests</strong>: boot Karaf, talk to Elasticsearch (or Docker), wait. + That still matters for the live stack. It is the wrong loop for “does <code>ProfileServiceImpl</code> handle this edge case?”</p> + <p> + <a href="https://issues.apache.org/jira/browse/UNOMI-881">UNOMI-881</a> + / + <a href="https://github.com/apache/unomi/pull/772">#772</a> + / + <a href="https://github.com/apache/unomi/pull/776">#776</a> + added a unit-test harness centered on one class:</p> + + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>services/src/test/java/org/apache/unomi/services/impl/InMemoryPersistenceServiceImpl.java</code></pre> + + <img src="/assets/images/unomi3/unit-harness-vs-it.svg" class="img-fluid my-4" alt="Unit tests with InMemoryPersistenceServiceImpl versus full integration tests"/> + + <h2 class="h4 mt-4 mb-3">What it is (and is not)</h2> + <p>It is <strong>not</strong> a production persistence engine. It is a complete <code>PersistenceService</code> implementation living only on the <strong>test</strong> classpath.</p> + + <h2 class="h4 mt-5 mb-3">Capabilities</h2> + <ul> + <li><strong>Full SPI surface</strong> — save/load/remove, query (condition + field), range query, paginated <code>PartialList</code>, remove-by-query</li> + <li><strong>Real condition evaluation</strong> — <code>ConditionEvaluatorDispatcher</code> (not a stubbed query layer)</li> + <li><strong>In-memory store</strong> — <code>ConcurrentHashMap</code> keyed items; optional JSON file mirror under <code>target/persistence-data</code> (pretty-print, clear-on-init)</li> + <li><strong>Refresh simulation</strong> — optional refresh delay thread; per-item-type policies <code>FALSE</code> / <code>TRUE</code> / <code>WAIT_FOR</code>; <code>refresh()</code> API</li> + <li><strong>Scroll</strong> — scroll id + time validity like ES/OS style paging</li> + <li><strong>Default query limit</strong> — mirrors search-engine default size when callers pass negative size</li> + <li><strong>Property mappings</strong> — set/get property mapping metadata used by services</li> + <li><strong>Aggregates</strong> — persistence aggregate SPI support used by analytics-style service tests</li> + <li><strong>Sequences / primary terms</strong> — index sequence helpers for ID generation scenarios</li> + <li><strong>System-item ID conventions</strong> — same <code>tenantId_itemId_itemType</code> rules as ES/OS for system types (rules, segments, schemas, …)</li> + <li><strong>Tenant transformation listeners</strong> — register listeners like production persistence</li> + <li><strong>Batching / overwrite flags</strong> — <code>save(item, useBatching, alwaysOverwrite)</code> overloads</li> + <li><strong>Lifecycle</strong> — <code>shutdown()</code> to stop refresh threads between tests</li> + </ul> + + <h2 class="h4 mt-5 mb-3">How tests use it</h2> + <p>Typical Surefire tests construct the harness, wire a real <code>*ServiceImpl</code>, and assert behavior without OSGi:</p> + <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>persistenceService = new InMemoryPersistenceServiceImpl( + executionContextManager, conditionEvaluatorDispatcher); + +// then inject into ProfileServiceImpl, EventServiceImpl, +// RulesServiceImpl, SegmentServiceImpl, …</code></pre> + <p>Concrete adopters in-tree include <code>ProfileServiceImplTest</code>, <code>EventServiceImplTest</code>, <code>RulesServiceImplTest</code>, <code>SegmentServiceImplTest</code>, and the self-test <code>InMemoryPersistenceServiceImplTest</code>.</p> + + <h2 class="h4 mt-5 mb-3">Unit vs integration</h2> + <div class="table-responsive"> + <table class="table table-sm align-middle"> + <thead><tr><th></th><th>Integration tests</th><th>Harness unit tests</th></tr></thead> + <tbody> + <tr><td>Speed</td><td>Minutes+</td><td>Seconds</td></tr> + <tr><td>Isolation</td><td>Shared cluster state</td><td>Fresh store per test</td></tr> + <tr><td>Scope</td><td>Full platform</td><td>One service / scenario</td></tr> + <tr><td>Fidelity</td><td>Production ES/OS</td><td>SPI + conditions + refresh simulation</td></tr> + </tbody> + </table> + </div> + <p>ITs prove the live stack (and pair with the + <a href="/blog/unomi-3-it-execution.html">clearer IT console</a>). + The harness is why Unomi 3 could grow high-signal <code>@Test</code> coverage around services without waiting on Docker every time.</p> + + <p class="mt-4"> + <a class="btn btn-primary" href="https://github.com/apache/unomi/blob/master/services/src/test/java/org/apache/unomi/services/impl/InMemoryPersistenceServiceImpl.java">View on GitHub</a> + <a class="btn btn-outline-primary ms-2" href="/contribute/testing.html">Contribute · Testing</a> + <a class="btn btn-outline-primary ms-2" href="/blog/unomi-3-it-execution.html">IT execution</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/sitemap.xml b/src/main/webapp/sitemap.xml index 69901b5..e2030eb 100644 --- a/src/main/webapp/sitemap.xml +++ b/src/main/webapp/sitemap.xml @@ -214,6 +214,56 @@ <changefreq>monthly</changefreq> <priority>0.85</priority> </url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-it-execution.html</loc> + <changefreq>monthly</changefreq> + <priority>0.75</priority> + </url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-manual.html</loc> + <changefreq>monthly</changefreq> + <priority>0.8</priority> + </url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-build-sh.html</loc> + <changefreq>monthly</changefreq> + <priority>0.75</priority> + </url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-platform-foundation.html</loc> + <changefreq>monthly</changefreq> + <priority>0.8</priority> + </url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-opensearch.html</loc> + <changefreq>monthly</changefreq> + <priority>0.8</priority> + </url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-unit-testing-harness.html</loc> + <changefreq>monthly</changefreq> + <priority>0.75</priority> + </url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-shell-commands.html</loc> + <changefreq>monthly</changefreq> + <priority>0.8</priority> + </url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-task-scheduler.html</loc> + <changefreq>monthly</changefreq> + <priority>0.75</priority> + </url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-request-tracing.html</loc> + <changefreq>monthly</changefreq> + <priority>0.8</priority> + </url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-openapi-enrichment.html</loc> + <changefreq>monthly</changefreq> + <priority>0.8</priority> + </url> <url> <loc>https://unomi.apache.org/llms.txt</loc> <lastmod>2026-07-14</lastmod>
