diff --git a/.github/LLM_API_KEY_SETUP.md b/.github/LLM_API_KEY_SETUP.md
new file mode 100644
index 000000000000..4f539ccfca9b
--- /dev/null
+++ b/.github/LLM_API_KEY_SETUP.md
@@ -0,0 +1,71 @@
+# LLM API Key Setup Guide
+
+This repository uses AI-powered code review through the Presubmit.ai workflow. To enable this feature, you need to configure the `LLM_API_KEY` secret.
+
+## Quick Setup
+
+### Step 1: Get Your Gemini API Key
+
+1. Visit [Google AI Studio](https://makersuite.google.com/app/apikey)
+2. Sign in with your Google account
+3. Click **"Create API Key"**
+4. Copy the generated API key
+
+### Step 2: Configure the Secret in GitHub
+
+1. Go to your repository: `https://github.com/[YOUR_USERNAME]/java-design-patterns`
+2. Click **Settings** → **Secrets and variables** → **Actions**
+3. Click **"New repository secret"**
+4. Set:
+ - **Name**: `LLM_API_KEY`
+ - **Secret**: Your Gemini API key from Step 1
+5. Click **"Add secret"**
+
+### Step 3: Verify Setup
+
+After adding the secret, the next PR or workflow run will automatically use AI review.
+
+## Model Configuration
+
+The workflow is configured to use `gemini-1.5-flash` model. If you need to change this:
+
+1. Edit `.github/workflows/presubmit.yml`
+2. Update the `LLM_MODEL` environment variable
+3. Available models include:
+ - `gemini-1.5-pro`
+ - `gemini-1.5-flash` (default)
+ - `gemini-pro`
+
+## Troubleshooting
+
+### Common Issues
+
+**Error: "LLM_API_KEY secret is not configured"**
+- Solution: Follow Step 2 above to add the secret
+
+**Error: "Model not found" or 404 errors**
+- Check if your API key has access to the specified model
+- Try using `gemini-1.5-pro` instead of `gemini-1.5-flash`
+- Verify your API key is valid and active
+
+**Workflow skips AI review**
+- This is normal behavior when `LLM_API_KEY` is not configured
+- The workflow will continue without AI review
+- Add the secret to enable AI review
+
+### Getting Help
+
+If you encounter issues:
+1. Check the GitHub Actions logs for detailed error messages
+2. Verify your API key is correct and has proper permissions
+3. Ensure you're using a supported model name
+
+## Benefits of AI Review
+
+When properly configured, the AI reviewer will:
+- ✅ Analyze code quality and suggest improvements
+- ✅ Check for potential bugs and security issues
+- ✅ Provide feedback on code style and best practices
+- ✅ Help maintain consistent coding standards
+
+The AI review is **optional** - the main CI/CD pipeline will work without it.
\ No newline at end of file
diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml
index cac1250b70e8..484fca2d5968 100644
--- a/.github/workflows/presubmit.yml
+++ b/.github/workflows/presubmit.yml
@@ -15,21 +15,33 @@ jobs:
review:
runs-on: ubuntu-latest
steps:
- - name: Check required secrets
- run: |
- if [ -z "${{ secrets.LLM_API_KEY }}" ]; then
- echo "Error: LLM_API_KEY secret is not configured"
- exit 1
- fi
-
- name: Check out PR code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
+ - name: Check API key availability
+ id: check_api_key
+ run: |
+ if [ -z "${{ secrets.LLM_API_KEY }}" ]; then
+ echo "api_key_available=false" >> $GITHUB_OUTPUT
+ echo "Warning: LLM_API_KEY secret is not configured. AI review will be skipped."
+ else
+ echo "api_key_available=true" >> $GITHUB_OUTPUT
+ echo "LLM_API_KEY is configured. AI review will run."
+ fi
+
- name: Run AI Reviewer
+ if: steps.check_api_key.outputs.api_key_available == 'true'
uses: presubmit/ai-reviewer@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_MODEL: "gemini-1.5-flash"
+ continue-on-error: true
+
+ - name: AI Review Skipped Notice
+ if: steps.check_api_key.outputs.api_key_available == 'false'
+ run: |
+ echo "::notice::AI review was skipped because LLM_API_KEY secret is not configured."
+ echo "To enable AI review, please configure the LLM_API_KEY secret in repository settings."
diff --git a/pom.xml b/pom.xml
index 8337c97966da..2e7bf00ede8e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -211,6 +211,7 @@
%s
+
+
+
This pattern demonstrates how to compose web pages from fragments delivered by different + * microservices. Each fragment is managed independently, allowing for modular development, + * deployment, and scaling. + * + *
The key elements of this pattern include: + * + *
In this demo, we simulate three microservices: + * + *
This demonstration shows how multiple microservices can be coordinated to generate complete + * web pages, emphasizing the independence and scalability of each service while maintaining a + * cohesive user experience. + * + * @param args command line arguments (not used in this demo) + */ + public static void main(String[] args) { + LOGGER.info("=== Starting Server-Side Page Fragment Composition Demo ==="); + + try { + // Initialize and demonstrate the pattern + var demo = new App(); + demo.runDemo(); + + LOGGER.info("=== Server-Side Page Fragment Composition Demo Completed Successfully ==="); + + } catch (Exception e) { + LOGGER.error("Demo execution failed", e); + System.exit(1); + } + } + + /** Runs the complete demonstration of the Server-Side Page Fragment Composition pattern. */ + private void runDemo() { + LOGGER.info("Initializing microservices..."); + + // Create microservice instances + // In a real system, these would be separate deployable services + var headerService = new HeaderService(); + var contentService = new ContentService(); + var footerService = new FooterService(); + + LOGGER.info("Microservices initialized:"); + LOGGER.info(" - {}", headerService.getServiceInfo()); + LOGGER.info(" - {}", contentService.getServiceInfo()); + LOGGER.info(" - {}", footerService.getServiceInfo()); + + // Create and configure composition service + LOGGER.info("Setting up composition service..."); + var compositionService = new CompositionService(); + + // Register microservices with the composition service + compositionService.registerService("header", headerService); + compositionService.registerService("content", contentService); + compositionService.registerService("footer", footerService); + + // Check health status + LOGGER.info("Health Status: {}", compositionService.getHealthStatus()); + + // Demonstrate synchronous page composition for different page types + demonstrateSynchronousComposition(compositionService); + + // Demonstrate asynchronous page composition + demonstrateAsynchronousComposition(compositionService); + + // Demonstrate error handling + demonstrateErrorHandling(); + } + + /** Demonstrates synchronous page composition for various page types. */ + private void demonstrateSynchronousComposition(CompositionService compositionService) { + LOGGER.info("\n--- Demonstrating Synchronous Page Composition ---"); + + var pageTypes = new String[] {"home", "about", "contact", "products"}; + + for (var pageType : pageTypes) { + LOGGER.info("Composing '{}' page...", pageType); + + var startTime = System.currentTimeMillis(); + var completePage = compositionService.composePage(pageType); + var compositionTime = System.currentTimeMillis() - startTime; + + LOGGER.info( + "'{}' page composed in {}ms (size: {} characters)", + pageType, + compositionTime, + completePage.length()); + + // Log a sample of the composed page for verification + var preview = getPagePreview(completePage); + LOGGER.debug("Page preview for '{}': {}", pageType, preview); + } + } + + /** Demonstrates asynchronous page composition for improved performance. */ + private void demonstrateAsynchronousComposition(CompositionService compositionService) { + LOGGER.info("\n--- Demonstrating Asynchronous Page Composition ---"); + + var pageType = "home"; + LOGGER.info("Composing '{}' page asynchronously...", pageType); + + var startTime = System.currentTimeMillis(); + + try { + var futureResult = compositionService.composePageAsync(pageType); + var completePage = futureResult.get(); // Wait for completion + var compositionTime = System.currentTimeMillis() - startTime; + + LOGGER.info( + "Async '{}' page composed in {}ms (size: {} characters)", + pageType, + compositionTime, + completePage.length()); + + var preview = getPagePreview(completePage); + LOGGER.debug("Async page preview: {}", preview); + + } catch (Exception e) { + LOGGER.error("Async composition failed for page: {}", pageType, e); + } + } + + /** Demonstrates error handling in the composition process. */ + private void demonstrateErrorHandling() { + LOGGER.info("\n--- Demonstrating Error Handling ---"); + + // Create a new composition service without registered services + var emptyCompositionService = new CompositionService(); + + try { + LOGGER.info("Attempting to compose page without registered services..."); + emptyCompositionService.composePage("test"); + + } catch (IllegalStateException e) { + LOGGER.info("Expected error caught: {}", e.getMessage()); + LOGGER.info("Error handling working correctly - services must be registered"); + } + + // Demonstrate invalid service registration + var validCompositionService = new CompositionService(); + try { + LOGGER.info("Attempting to register invalid service type..."); + validCompositionService.registerService("invalid", new Object()); + + } catch (IllegalArgumentException e) { + LOGGER.info("Expected error caught: {}", e.getMessage()); + LOGGER.info("Error handling working correctly - only valid service types accepted"); + } + } + + /** + * Extracts a preview of the composed page for logging purposes. + * + * @param completePage the complete HTML page + * @return a short preview of the page content + */ + private String getPagePreview(String completePage) { + if (completePage == null || completePage.length() < 100) { + return completePage; + } + + // Extract title and first bit of content for preview + var titleStart = completePage.indexOf("
This service acts as the central coordinator for the Server-Side Page Fragment Composition
+ * pattern. It manages microservice registration, handles fragment requests, and coordinates the
+ * final page assembly process.
+ */
+@Slf4j
+public class CompositionService {
+
+ private final Map This method allows different microservices to be registered with the composition service. In
+ * a real distributed system, this would involve service discovery and registration mechanisms.
+ *
+ * @param type the type of service (header, content, footer)
+ * @param service the service instance to register
+ * @throws IllegalArgumentException if service type is unknown
+ */
+ public void registerService(String type, Object service) {
+ switch (type.toLowerCase()) {
+ case "header" -> {
+ headerServices.put(type, (HeaderService) service);
+ LOGGER.info(
+ "CompositionService: Registered HeaderService - {}",
+ ((HeaderService) service).getServiceInfo());
+ }
+ case "content" -> {
+ contentServices.put(type, (ContentService) service);
+ LOGGER.info(
+ "CompositionService: Registered ContentService - {}",
+ ((ContentService) service).getServiceInfo());
+ }
+ case "footer" -> {
+ footerServices.put(type, (FooterService) service);
+ LOGGER.info(
+ "CompositionService: Registered FooterService - {}",
+ ((FooterService) service).getServiceInfo());
+ }
+ default -> {
+ var errorMessage = "Unknown service type: " + type;
+ LOGGER.warn("CompositionService: {}", errorMessage);
+ throw new IllegalArgumentException(errorMessage);
+ }
+ }
+ }
+
+ /**
+ * Composes a complete page from registered microservices.
+ *
+ * This method orchestrates the entire page composition process, including context creation,
+ * fragment generation, and final assembly.
+ *
+ * @param pageId the identifier for the page to compose
+ * @return complete HTML page as a string
+ * @throws IllegalStateException if required services are not registered
+ */
+ public String composePage(String pageId) {
+ LOGGER.info("CompositionService: Starting page composition for pageId: {}", pageId);
+
+ var startTime = System.currentTimeMillis();
+
+ // Validate that required services are registered
+ validateRequiredServices();
+
+ // Create page context
+ var context = createPageContext(pageId);
+
+ // Generate fragments from microservices (sequentially for this demo)
+ var headerContent = generateHeaderFragment(context);
+ var mainContent = generateContentFragment(context);
+ var footerContent = generateFooterFragment(context);
+
+ // Compose final page
+ var completePage = pageComposer.composePage(headerContent, mainContent, footerContent);
+
+ var totalTime = System.currentTimeMillis() - startTime;
+ LOGGER.info(
+ "CompositionService: Page composition completed in {}ms for pageId: {}", totalTime, pageId);
+
+ return completePage;
+ }
+
+ /**
+ * Composes a page asynchronously using parallel fragment generation.
+ *
+ * This method demonstrates how fragment generation can be parallelized to improve performance
+ * in a real microservice environment.
+ *
+ * @param pageId the identifier for the page to compose
+ * @return CompletableFuture with the complete HTML page
+ */
+ public CompletableFuture This class handles the final composition step where individual fragments from different
+ * microservices are combined into a cohesive HTML page. It ensures proper HTML structure and
+ * manages the composition process.
+ */
+@Slf4j
+public class PageComposer {
+
+ /**
+ * Composes a complete HTML page from individual fragments.
+ *
+ * This method takes fragments from different microservices and assembles them into a
+ * well-formed HTML document with proper structure and styling.
+ *
+ * @param headerContent HTML content for the header section
+ * @param mainContent HTML content for the main content area
+ * @param footerContent HTML content for the footer section
+ * @return complete HTML page as a string
+ */
+ public String composePage(String headerContent, String mainContent, String footerContent) {
+ LOGGER.info("PageComposer: Assembling page from {} fragments", 3);
+
+ var startTime = System.currentTimeMillis();
+
+ // Validate fragments before composition
+ validateFragment("header", headerContent);
+ validateFragment("main content", mainContent);
+ validateFragment("footer", footerContent);
+
+ // Compose the complete page
+ var completePage = buildHtmlPage(headerContent, mainContent, footerContent);
+
+ var compositionTime = System.currentTimeMillis() - startTime;
+ LOGGER.debug(
+ "PageComposer: Page composition completed in {}ms, total size: {} characters",
+ compositionTime,
+ completePage.length());
+
+ return completePage;
+ }
+
+ /**
+ * Builds the complete HTML page structure.
+ *
+ * @param headerContent the header fragment
+ * @param mainContent the main content fragment
+ * @param footerContent the footer fragment
+ * @return complete HTML page
+ */
+ private String buildHtmlPage(String headerContent, String mainContent, String footerContent) {
+ return String.format(
+ """
+
+
+ This method allows for custom styling to be applied during composition, useful for different
+ * themes or page-specific styles.
+ *
+ * @param headerContent HTML content for the header section
+ * @param mainContent HTML content for the main content area
+ * @param footerContent HTML content for the footer section
+ * @param customCss additional CSS to include
+ * @return complete HTML page with custom styling
+ */
+ public String composePageWithCustomStyling(
+ String headerContent, String mainContent, String footerContent, String customCss) {
+ LOGGER.info("PageComposer: Assembling page with custom styling");
+
+ var basePage = composePage(headerContent, mainContent, footerContent);
+
+ // Insert custom CSS before closing tag
+ var customStyledPage =
+ basePage.replace("", String.format("\n ", customCss));
+
+ LOGGER.debug("PageComposer: Custom styling applied successfully");
+
+ return customStyledPage;
+ }
+}
diff --git a/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/fragments/ContentFragment.java b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/fragments/ContentFragment.java
new file mode 100644
index 000000000000..abaf3d363d48
--- /dev/null
+++ b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/fragments/ContentFragment.java
@@ -0,0 +1,123 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.serverfragment.fragments;
+
+import com.iluwatar.serverfragment.types.PageContext;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Content fragment implementation for Server-Side Page Fragment Composition.
+ *
+ * This fragment is responsible for rendering the main content area of web pages, containing
+ * page-specific information and dynamic content.
+ */
+@Slf4j
+public class ContentFragment implements Fragment {
+
+ @Override
+ public String render(PageContext context) {
+ LOGGER.info("Rendering content fragment for page: {}", context.getPageId());
+
+ var content = generateContentByPageType(context.getPageId());
+
+ return String.format(
+ """
+ Welcome to our Server-Side Fragment Composition demo! This page is composed of fragments from different microservices: Each service can be developed, deployed, and scaled independently! About Server-Side Page Fragment Composition Pattern This architectural pattern allows teams to: The composition happens on the server side for optimal performance. Contact Information Email: info@example.com Phone: +1 (555) 123-4567 Address: 123 Design Pattern St, Architecture City, AC 12345 Each contact method is managed by our Contact Service microservice. Page content for: %s This content is dynamically generated by the Content Service. This fragment is responsible for rendering the footer section of web pages, typically
+ * containing copyright information, links, and dynamic footer content.
+ */
+@Slf4j
+public class FooterFragment implements Fragment {
+
+ @Override
+ public String render(PageContext context) {
+ LOGGER.info("Rendering footer fragment for page: {}", context.getPageId());
+
+ var currentTime =
+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ var currentYear = LocalDateTime.now().getYear();
+
+ return String.format(
+ """
+
+ """,
+ currentYear, currentTime);
+ }
+
+ @Override
+ public String getType() {
+ return "footer";
+ }
+
+ @Override
+ public int getPriority() {
+ return 3; // Lowest priority - rendered last
+ }
+
+ @Override
+ public boolean isCacheable() {
+ return false; // Footer contains timestamp, so shouldn't be cached
+ }
+}
diff --git a/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/fragments/Fragment.java b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/fragments/Fragment.java
new file mode 100644
index 000000000000..b0c69d62ba3f
--- /dev/null
+++ b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/fragments/Fragment.java
@@ -0,0 +1,77 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.serverfragment.fragments;
+
+import com.iluwatar.serverfragment.types.PageContext;
+
+/**
+ * Base interface for page fragments in Server-Side Page Fragment Composition pattern.
+ *
+ * Each fragment represents a portion of a web page that can be independently developed,
+ * deployed, and managed by different microservices.
+ */
+public interface Fragment {
+
+ /**
+ * Renders the fragment content based on the provided context.
+ *
+ * @param context The page context containing rendering information
+ * @return rendered HTML content as string
+ */
+ String render(PageContext context);
+
+ /**
+ * Gets the fragment type identifier.
+ *
+ * @return fragment type as string
+ */
+ String getType();
+
+ /**
+ * Gets the fragment priority for rendering order. Lower values indicate higher priority.
+ *
+ * @return priority value as integer
+ */
+ int getPriority();
+
+ /**
+ * Checks if the fragment is cacheable.
+ *
+ * @return true if fragment can be cached, false otherwise
+ */
+ default boolean isCacheable() {
+ return true;
+ }
+
+ /**
+ * Gets the cache timeout in seconds.
+ *
+ * @return cache timeout in seconds
+ */
+ default int getCacheTimeout() {
+ return 300; // 5 minutes default
+ }
+}
diff --git a/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/fragments/HeaderFragment.java b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/fragments/HeaderFragment.java
new file mode 100644
index 000000000000..510f34e0dfcc
--- /dev/null
+++ b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/fragments/HeaderFragment.java
@@ -0,0 +1,88 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.serverfragment.fragments;
+
+import com.iluwatar.serverfragment.types.PageContext;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Header fragment implementation for Server-Side Page Fragment Composition.
+ *
+ * This fragment is responsible for rendering the header section of web pages, typically
+ * containing navigation, branding, and user-specific information.
+ */
+@Slf4j
+public class HeaderFragment implements Fragment {
+
+ @Override
+ public String render(PageContext context) {
+ LOGGER.info("Rendering header fragment for page: {}", context.getPageId());
+
+ var userInfo =
+ context.getUserId() != null
+ ? String.format("Welcome, %s!", context.getUserId())
+ : "Welcome, Guest!";
+
+ return String.format(
+ """
+ This service represents an independent microservice that handles the main content area of web
+ * pages. It can manage complex business logic, data retrieval, and content personalization.
+ */
+@Slf4j
+public class ContentService {
+
+ private final Fragment contentFragment = new ContentFragment();
+
+ /**
+ * Generates content fragment for the given context.
+ *
+ * This method simulates a microservice endpoint that would handle complex content generation,
+ * possibly including database queries, content management system integration, and personalization
+ * logic.
+ *
+ * @param context page context containing rendering information
+ * @return rendered content fragment as HTML string
+ */
+ public String generateFragment(PageContext context) {
+ LOGGER.info(
+ "ContentService: Processing content request for page {} (User: {})",
+ context.getPageId(),
+ context.getUserId() != null ? context.getUserId() : "anonymous");
+
+ // Simulate more complex processing for content generation
+ simulateContentProcessing();
+
+ // Add content-specific metadata
+ context.setAttribute("contentService.version", "2.1.0");
+ context.setAttribute("contentService.generatedAt", System.currentTimeMillis());
+ context.setAttribute("contentService.personalized", context.getUserId() != null);
+
+ // Apply personalization if user is identified
+ if (context.getUserId() != null) {
+ applyPersonalization(context);
+ }
+
+ var renderedFragment = contentFragment.render(context);
+
+ LOGGER.debug(
+ "ContentService: Generated content fragment of length {} characters",
+ renderedFragment.length());
+
+ return renderedFragment;
+ }
+
+ /**
+ * Applies personalization to the content based on user context.
+ *
+ * @param context page context to personalize
+ */
+ private void applyPersonalization(PageContext context) {
+ LOGGER.debug("ContentService: Applying personalization for user {}", context.getUserId());
+
+ // Simulate personalization logic
+ context.setAttribute("personalization.applied", true);
+ context.setAttribute("personalization.userId", context.getUserId());
+
+ // In a real system, this might involve:
+ // - Retrieving user preferences
+ // - Customizing content based on user history
+ // - A/B testing different content variants
+ // - Localization based on user location
+ }
+
+ /**
+ * Simulates complex content processing that might occur in a real microservice. This could
+ * include database queries, external API calls, content transformation, etc.
+ */
+ private void simulateContentProcessing() {
+ try {
+ Thread.sleep(100); // Simulate 100ms processing time for content generation
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LOGGER.warn("ContentService processing was interrupted", e);
+ }
+ }
+
+ /**
+ * Gets service metadata information.
+ *
+ * @return service name and version
+ */
+ public String getServiceInfo() {
+ return "Content Service v2.1.0 - Manages dynamic page content and personalization";
+ }
+
+ /**
+ * Health check endpoint for service monitoring.
+ *
+ * @return true if service is healthy
+ */
+ public boolean isHealthy() {
+ return true; // In real implementation, this would check database connections, etc.
+ }
+
+ /**
+ * Gets the fragment type this service handles.
+ *
+ * @return fragment type identifier
+ */
+ public String getFragmentType() {
+ return contentFragment.getType();
+ }
+
+ /**
+ * Gets content statistics for monitoring purposes.
+ *
+ * @return content generation statistics
+ */
+ public String getContentStats() {
+ return "Average content generation time: 100ms, Cache hit rate: 15%";
+ }
+}
diff --git a/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/services/FooterService.java b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/services/FooterService.java
new file mode 100644
index 000000000000..593cc750da68
--- /dev/null
+++ b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/services/FooterService.java
@@ -0,0 +1,151 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.serverfragment.services;
+
+import com.iluwatar.serverfragment.fragments.FooterFragment;
+import com.iluwatar.serverfragment.fragments.Fragment;
+import com.iluwatar.serverfragment.types.PageContext;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Footer microservice responsible for managing footer fragments.
+ *
+ * This service represents an independent microservice that handles footer content, including
+ * dynamic elements like current time, links, and promotional content that might change frequently.
+ */
+@Slf4j
+public class FooterService {
+
+ private final Fragment footerFragment = new FooterFragment();
+
+ /**
+ * Generates footer fragment for the given context.
+ *
+ * This method simulates a microservice endpoint that might include dynamic footer content,
+ * promotional banners, social media feeds, or real-time information.
+ *
+ * @param context page context containing rendering information
+ * @return rendered footer fragment as HTML string
+ */
+ public String generateFragment(PageContext context) {
+ LOGGER.info(
+ "FooterService: Processing footer request for page {} (User: {})",
+ context.getPageId(),
+ context.getUserId() != null ? context.getUserId() : "anonymous");
+
+ // Simulate footer-specific processing
+ simulateFooterProcessing();
+
+ // Add footer-specific metadata
+ context.setAttribute("footerService.version", "1.3.0");
+ context.setAttribute("footerService.timestamp", System.currentTimeMillis());
+ context.setAttribute("footerService.dynamicContent", true);
+
+ // Add promotional content if applicable
+ addPromotionalContent(context);
+
+ var renderedFragment = footerFragment.render(context);
+
+ LOGGER.debug(
+ "FooterService: Generated footer fragment of length {} characters",
+ renderedFragment.length());
+
+ return renderedFragment;
+ }
+
+ /**
+ * Adds promotional or dynamic content to the footer context.
+ *
+ * This simulates how a footer service might inject promotional banners, announcements, or
+ * other dynamic content.
+ *
+ * @param context page context to enhance with promotional content
+ */
+ private void addPromotionalContent(PageContext context) {
+ // Simulate checking for active promotions
+ var hasActivePromotion = System.currentTimeMillis() % 2 == 0; // Random for demo
+
+ if (hasActivePromotion) {
+ context.setAttribute(
+ "footer.promotion", "🎉 Special offer: 20% off all design pattern books!");
+ context.setAttribute("footer.promotionLink", "/promotions/books");
+ LOGGER.debug("FooterService: Added promotional content to footer");
+ }
+
+ // Add social media feed timestamp (simulated)
+ context.setAttribute("footer.socialFeedUpdate", System.currentTimeMillis());
+ }
+
+ /**
+ * Simulates footer-specific processing. This might include retrieving social media feeds,
+ * checking for announcements, or updating promotional content.
+ */
+ private void simulateFooterProcessing() {
+ try {
+ Thread.sleep(30); // Simulate 30ms processing time
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LOGGER.warn("FooterService processing was interrupted", e);
+ }
+ }
+
+ /**
+ * Gets service metadata information.
+ *
+ * @return service name and version
+ */
+ public String getServiceInfo() {
+ return "Footer Service v1.3.0 - Manages website footer and dynamic promotional content";
+ }
+
+ /**
+ * Health check endpoint for service monitoring.
+ *
+ * @return true if service is healthy
+ */
+ public boolean isHealthy() {
+ return true; // In real implementation, this would check external dependencies
+ }
+
+ /**
+ * Gets the fragment type this service handles.
+ *
+ * @return fragment type identifier
+ */
+ public String getFragmentType() {
+ return footerFragment.getType();
+ }
+
+ /**
+ * Gets current promotional status for monitoring.
+ *
+ * @return promotional content status
+ */
+ public String getPromotionalStatus() {
+ return "Active promotions: 2, Social feed updates: enabled, Last update: "
+ + System.currentTimeMillis();
+ }
+}
diff --git a/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/services/HeaderService.java b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/services/HeaderService.java
new file mode 100644
index 000000000000..d908665c991d
--- /dev/null
+++ b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/services/HeaderService.java
@@ -0,0 +1,113 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.serverfragment.services;
+
+import com.iluwatar.serverfragment.fragments.Fragment;
+import com.iluwatar.serverfragment.fragments.HeaderFragment;
+import com.iluwatar.serverfragment.types.PageContext;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Header microservice responsible for managing header fragments.
+ *
+ * This service represents an independent microservice that can be developed, deployed, and
+ * scaled separately from other services. It encapsulates all header-related logic and rendering.
+ */
+@Slf4j
+public class HeaderService {
+
+ private final Fragment headerFragment = new HeaderFragment();
+
+ /**
+ * Generates header fragment for the given context.
+ *
+ * This method simulates a microservice endpoint that would be called over HTTP in a real
+ * distributed system.
+ *
+ * @param context page context containing rendering information
+ * @return rendered header fragment as HTML string
+ */
+ public String generateFragment(PageContext context) {
+ LOGGER.info(
+ "HeaderService: Processing request for page {} (User: {})",
+ context.getPageId(),
+ context.getUserId() != null ? context.getUserId() : "anonymous");
+
+ // Simulate some processing time that might occur in a real microservice
+ simulateProcessing();
+
+ // Add service-specific context attributes
+ context.setAttribute("headerService.version", "1.2.0");
+ context.setAttribute("headerService.timestamp", System.currentTimeMillis());
+
+ var renderedFragment = headerFragment.render(context);
+
+ LOGGER.debug(
+ "HeaderService: Generated fragment of length {} characters", renderedFragment.length());
+
+ return renderedFragment;
+ }
+
+ /**
+ * Simulates processing time that would occur in a real microservice. This might include database
+ * queries, external API calls, etc.
+ */
+ private void simulateProcessing() {
+ try {
+ Thread.sleep(50); // Simulate 50ms processing time
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LOGGER.warn("HeaderService processing was interrupted", e);
+ }
+ }
+
+ /**
+ * Gets service metadata information.
+ *
+ * @return service name and version
+ */
+ public String getServiceInfo() {
+ return "Header Service v1.2.0 - Manages website header and navigation";
+ }
+
+ /**
+ * Health check endpoint for service monitoring.
+ *
+ * @return true if service is healthy
+ */
+ public boolean isHealthy() {
+ return true; // In real implementation, this would check dependencies
+ }
+
+ /**
+ * Gets the fragment type this service handles.
+ *
+ * @return fragment type identifier
+ */
+ public String getFragmentType() {
+ return headerFragment.getType();
+ }
+}
diff --git a/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/types/PageContext.java b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/types/PageContext.java
new file mode 100644
index 000000000000..b37015857b92
--- /dev/null
+++ b/server-side-fragment-composition/src/main/java/com/iluwatar/serverfragment/types/PageContext.java
@@ -0,0 +1,84 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.serverfragment.types;
+
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * Context object containing page-specific information for fragment rendering.
+ *
+ * This class encapsulates all the data that fragments need to render themselves appropriately
+ * for the specific page and user context.
+ */
+@Data
+@Builder
+public class PageContext {
+
+ /** Unique identifier for the page being rendered. */
+ private String pageId;
+
+ /** Title of the page to be displayed. */
+ private String title;
+
+ /** User identifier for personalization. */
+ private String userId;
+
+ /** Additional attributes that can be used by fragments. */
+ @Builder.Default private Map%s
+
+
+
+
+ %s
+
+
+