This document describes the package organization of Source Code Portal, including the responsibilities of each package and key classes.
- Overview
- Package Hierarchy
- Core Packages
- Domain Packages
- Controller Packages
- Infrastructure Packages
- Configuration Packages
- Testing Packages
- Package Dependencies
Source Code Portal follows a layered architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ (Controllers, REST APIs, Web Pages) │
├─────────────────────────────────────────────────────────┤
│ Business Logic Layer │
│ (Domain Services, Commands, Fetch) │
├─────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ (Cache, Executor, Configuration) │
├─────────────────────────────────────────────────────────┤
│ External Integration Layer │
│ (GitHub, Jenkins, Snyk, Shields.io) │
└─────────────────────────────────────────────────────────┘
All packages follow the base namespace: no.cantara.docsite
Rationale:
no= Norway (country code)cantara= Organization namedocsite= Project name (historical, now "Source Code Portal")
no.cantara.docsite/
├── SpringBootServer.java # Spring Boot entry point
├── Server.java # Legacy Undertow entry point (deprecated)
│
├── cache/ # Caching layer
│ ├── CacheStore.java
│ ├── CacheKey.java
│ ├── CacheRepositoryKey.java
│ ├── CacheGroupKey.java
│ └── CacheShaKey.java
│
├── domain/ # Domain models and business logic
│ ├── config/ # Configuration models
│ ├── github/ # GitHub domain models
│ ├── renderer/ # Markdown/AsciiDoc rendering
│ ├── jenkins/ # Jenkins integration
│ ├── snyk/ # Snyk integration
│ ├── shields/ # Shields.io integration
│ └── scm/ # Source control abstractions
│
├── controller/ # HTTP request handlers
│ ├── spring/ # Spring MVC controllers (recommended)
│ ├── handler/ # Legacy Undertow handlers (deprecated)
│ ├── ApplicationController.java # Legacy router (deprecated)
│ └── WebController.java # Legacy web router (deprecated)
│
├── commands/ # Resilience4j command pattern
│ ├── BaseResilientCommand.java
│ ├── GetGitHubCommand.java
│ ├── GetCommand.java
│ └── GetShieldsCommand.java
│
├── fetch/ # Data fetching services
│ ├── PreFetchData.java
│ └── ScheduledFetchData.java
│
├── web/ # Web templating infrastructure
│ ├── ThymeleafViewEngineProcessor.java
│ ├── ResourceContext.java
│ └── WebHandler.java
│
├── executor/ # Thread pool management
│ ├── ExecutorService.java
│ └── ScheduledExecutorService.java
│
├── config/ # Spring Boot configuration
│ ├── ApplicationProperties.java
│ ├── CacheConfiguration.java
│ ├── ExecutorConfiguration.java
│ ├── WebMvcConfiguration.java
│ ├── ConfigurationBridge.java
│ └── SpringBootInitializer.java
│
├── actuator/ # Spring Boot Actuator components
│ ├── health/ # Custom health indicators
│ └── info/ # Custom info contributors
│
├── scheduled/ # Spring @Scheduled tasks
│ └── ScheduledFetchData.java
│
└── util/ # Utility classes
├── JsonUtil.java
├── DateUtil.java
└── UrlUtil.java
Purpose: Caching layer for GitHub data and external API responses.
Key Classes:
Central cache manager providing typed access to all caches.
@Component
public class CacheStore {
// Repository cache
public Repository getRepository(CacheRepositoryKey key);
public void putRepository(CacheRepositoryKey key, Repository repo);
// Commits cache
public List<Commit> getCommits(CacheKey key);
public void putCommits(CacheKey key, List<Commit> commits);
// Contents cache
public Contents getContents(CacheKey key);
public void putContents(CacheKey key, Contents contents);
// Build status cache
public BuildStatus getBuildStatus(CacheKey key);
public void putBuildStatus(CacheKey key, BuildStatus status);
// Cache management
public void invalidate(String cacheName, Object key);
public void clearCache(String cacheName);
public CacheStats getStats(String cacheName);
}Base cache key for org/repo/branch lookup.
public record CacheKey(
String organization,
String repository,
String branch
) {
// Used for: commits, contents, build status, releases
}Cache key for repository metadata with group support.
public record CacheRepositoryKey(
String groupId,
String organization,
String repository
) {
// Used for: repository metadata, grouped repos
}Dependencies:
- Spring Cache abstraction
- Caffeine cache (implementation)
- Domain models (Repository, Commit, etc.)
Purpose: Domain models and business logic.
Sub-packages:
Configuration loading and parsing.
Key Classes:
RepositoryConfigLoader.java- Loads config.json, fetches GitHub reposRepositoryGroup.java- Model for repository groupRepositoryConfig.java- Configuration data classConfigValidator.java- Validates configuration
Example:
@Component
public class RepositoryConfigLoader {
/**
* Load repository groups from config.json.
*/
public List<RepositoryGroup> getRepositoryGroups();
/**
* Get all repositories across all groups.
*/
public List<Repository> getAllRepositories();
/**
* Find group by ID.
*/
public RepositoryGroup getGroup(String groupId);
}GitHub API models and domain logic.
Key Classes:
Repository.java- GitHub repository modelCommit.java- Git commit modelRelease.java- GitHub release modelContents.java- Repository file contentsGitHubCommands.java- GitHub API operationsRateLimit.java- API rate limit info
Example:
public class Repository {
private String name;
private String organization;
private String description;
private String defaultBranch;
private int stars;
private int forks;
private Instant updatedAt;
private boolean isPrivate;
// Business logic
public boolean isActive();
public String getGitHubUrl();
}Markdown and AsciiDoc rendering.
Key Classes:
MarkdownRenderer.java- Markdown → HTML conversionAsciiDocRenderer.java- AsciiDoc → HTML conversionCodeHighlighter.java- Syntax highlightingLinkResolver.java- Resolve relative links
Example:
@Component
public class MarkdownRenderer {
/**
* Render Markdown to HTML.
*/
public String render(String markdown);
/**
* Render with custom base URL for link resolution.
*/
public String render(String markdown, String baseUrl);
}Jenkins build status integration.
Key Classes:
JenkinsClient.java- Jenkins API clientBuildStatus.java- Build status modelJenkinsConfig.java- Jenkins configuration
Snyk security test integration.
Key Classes:
SnykClient.java- Snyk API clientSecurityTestResult.java- Security test resultsVulnerability.java- Security vulnerability model
Shields.io badge integration.
Key Classes:
ShieldsClient.java- Shields.io API clientBadge.java- Badge modelBadgeStyle.java- Badge styling options
Source control management abstractions.
Key Classes:
ScmProvider.java- Interface for SCM providers (GitHub, GitLab, etc.)GitHubScmProvider.java- GitHub implementationScmRepository.java- Generic repository interface
Note: Currently only GitHub is supported, but this package provides abstraction for future multi-SCM support.
Purpose: HTTP request handlers (controllers and routing).
Sub-packages:
Spring MVC controllers for web and REST endpoints.
Web Controllers (@Controller):
DashboardWebController.java- Dashboard page (/, /dashboard)GroupWebController.java- Group view page (/group/{groupId})CommitsWebController.java- Commit history (/commits/{org}/{repo})ContentsWebController.java- Repository contents (/contents/{org}/{repo}/{branch})WikiWebController.java- Wiki pages (/wiki/{pageName})
REST Controllers (@RestController):
PingRestController.java- Ping endpoint (/ping)HealthRestController.java- Health checks (/health, /health/*)EchoRestController.java- Echo diagnostic (/echo)GitHubWebhookRestController.java- GitHub webhooks (/github/webhook)BadgeResourceController.java- SVG badges (/badge/*)
Example:
@Controller
public class DashboardWebController {
private final RepositoryConfigLoader configLoader;
private final CacheStore cacheStore;
public DashboardWebController(
RepositoryConfigLoader configLoader,
CacheStore cacheStore) {
this.configLoader = configLoader;
this.cacheStore = cacheStore;
}
@GetMapping("/dashboard")
public String dashboard(Model model) {
List<RepositoryGroup> groups = configLoader.getRepositoryGroups();
model.addAttribute("groups", groups);
return "index";
}
}Legacy Undertow web handlers.
Classes:
DashboardHandler.java- Dashboard page [@Deprecated]CardHandler.java- Group view [@Deprecated]CommitsHandler.java- Commit history [@Deprecated]ContentsHandler.java- Repository contents [@Deprecated]CantaraWikiHandler.java- Wiki pages [@Deprecated]BadgeResourceHandler.java- SVG badges [@Deprecated]
Status: Deprecated since 0.10.17-SNAPSHOT, scheduled for removal in 1.0.0.
ApplicationController.java- Main router [@Deprecated]WebController.java- Web page router [@Deprecated]
Migration: See DEPRECATED_UNDERTOW_CONTROLLERS.md for Spring MVC equivalents.
Purpose: Resilience4j command pattern for external HTTP calls.
Key Classes:
Base class with circuit breaker, bulkhead, and time limiter.
public abstract class BaseResilientCommand<T> {
protected final CircuitBreaker circuitBreaker;
protected final Bulkhead bulkhead;
protected final TimeLimiter timeLimiter;
protected BaseResilientCommand() {
// Circuit Breaker: 50% failure threshold, 60s open state
this.circuitBreaker = CircuitBreaker.of("default", CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(60))
.build());
// Bulkhead: 25 max concurrent calls
this.bulkhead = Bulkhead.of("default", BulkheadConfig.custom()
.maxConcurrentCalls(25)
.build());
// Time Limiter: 75s timeout
this.timeLimiter = TimeLimiter.of("default", TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(75))
.build());
}
protected abstract T executeCommand() throws Exception;
public T execute() {
return Decorators.ofSupplier(this::executeCommand)
.withCircuitBreaker(circuitBreaker)
.withBulkhead(bulkhead)
.withTimeLimiter(timeLimiter)
.get();
}
}GitHub API requests with circuit breaker.
public class GetGitHubCommand extends BaseResilientCommand<String> {
private final String url;
private final String accessToken;
public GetGitHubCommand(String url, String accessToken) {
super();
this.url = url;
this.accessToken = accessToken;
}
@Override
protected String executeCommand() throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + accessToken)
.header("Accept", "application/vnd.github.v3+json")
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
return response.body();
}
}Generic HTTP requests (Jenkins, Snyk).
Shields.io badge requests.
Benefits:
- Automatic retry on transient failures
- Circuit breaker prevents cascading failures
- Bulkhead limits concurrent calls
- Timeout protection
- Metrics and monitoring
Purpose: Data fetching services (startup and scheduled).
Key Classes:
Initial data population on startup.
@Component
public class PreFetchData implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
log.info("Starting data prefetch...");
// Prefetch repository metadata
prefetchRepositories();
// Prefetch recent commits
prefetchCommits();
// Prefetch README files
prefetchContents();
log.info("Data prefetch complete");
}
}Periodic background updates.
@Component
public class ScheduledFetchData {
/**
* Refresh repositories every 15 minutes.
*/
@Scheduled(fixedDelay = 900000, initialDelay = 60000)
public void refreshRepositories() {
// Refresh repository metadata
}
/**
* Refresh commits every 10 minutes.
*/
@Scheduled(cron = "0 */10 * * * *")
public void refreshCommits() {
// Refresh commit data
}
}Purpose: Web templating infrastructure.
Key Classes:
Thymeleaf template rendering.
@Component
public class ThymeleafViewEngineProcessor {
private final TemplateEngine templateEngine;
/**
* Render template with model data.
*/
public String render(String templateName, Map<String, Object> model);
/**
* Render to HTTP response (Spring MVC).
*/
public void renderToResponse(
String templateName,
Map<String, Object> model,
HttpServletResponse response
) throws IOException;
}Request path parsing and context.
public class ResourceContext {
private final String path;
private final Map<String, String> pathParams;
private final Map<String, String> queryParams;
public static ResourceContext parse(String requestPath);
public String getPathParam(String name);
public String getQueryParam(String name);
}Legacy handler interface (deprecated).
@Deprecated(since = "0.10.17-SNAPSHOT")
public interface WebHandler {
void handle(HttpServerExchange exchange);
}Purpose: Thread pool management.
Key Classes:
Async task executor using virtual threads.
@Configuration
public class ExecutorConfiguration {
@Bean
public ExecutorService executorService() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}Scheduled periodic tasks.
@Bean
public ScheduledExecutorService scheduledExecutorService() {
return Executors.newScheduledThreadPool(
5,
Thread.ofVirtual().factory()
);
}Note: In Spring Boot mode, prefer @Scheduled annotations over manual executor usage.
Purpose: Spring Boot configuration and setup.
Key Classes:
Type-safe configuration properties.
@Configuration
@ConfigurationProperties(prefix = "scp")
public class ApplicationProperties {
private Server server = new Server();
private Github github = new Github();
private Cache cache = new Cache();
public static class Server {
private String mode = "spring-boot";
private int port = 9090;
}
public static class Github {
private String accessToken;
private String organization;
}
}Cache manager and cache setup.
Thread pool configuration.
Spring MVC configuration (CORS, resource handlers).
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**")
.addResourceLocations("classpath:/META-INF/views/css/");
}
}Backward compatibility bridge for legacy properties.
Startup initialization tasks.
@Component
public class SpringBootInitializer implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
// Log startup info
// Validate configuration
// Check GitHub connectivity
}
}Purpose: Test infrastructure and test cases.
Key Test Classes:
JUnit 5 extension for embedded server testing.
@ExtendWith(TestServerExtension.class)
public class IntegrationTest {
@Test
public void testEndpoint(TestClient client) {
String response = client.get("/dashboard");
assertTrue(response.contains("Source Code Portal"));
}
}HTTP client utilities for testing.
public class TestClient {
public String get(String path);
public String post(String path, String body);
public HttpResponse<String> getResponse(String path);
}Utility to dump test data for debugging.
┌─────────────────────────────────────────────────────────┐
│ controller (Presentation) │
│ Depends on: domain, cache, web, commands │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ domain (Business Logic) │
│ Depends on: cache, commands, renderer │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ commands (External Calls) │
│ Depends on: executor, util │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ cache + executor (Infrastructure) │
│ Depends on: config, util │
└─────────────────────────────────────────────────────────┘
Controllers may depend on:
- ✅ domain (services, models)
- ✅ cache (CacheStore)
- ✅ web (ThymeleafViewEngineProcessor)
- ✅ config (ApplicationProperties)
- ❌ commands (use domain services instead)
Domain may depend on:
- ✅ cache (read/write)
- ✅ commands (external API calls)
- ✅ renderer (Markdown/AsciiDoc)
- ✅ config (configuration reading)
- ❌ controller (no circular dependencies)
- ❌ web (keep domain independent)
Commands may depend on:
- ✅ executor (thread pools)
- ✅ util (utilities)
- ❌ domain (keep infrastructure independent)
- ❌ cache (commands shouldn't cache directly)
Cache may depend on:
- ✅ config (cache configuration)
- ✅ util (utilities)
- ❌ domain (cache is infrastructure)
- ❌ commands (cache is lower level)
Rule: Lower layers must not depend on higher layers.
Layer Hierarchy (bottom to top):
- util (utilities)
- executor (thread pools)
- config (configuration)
- cache (caching infrastructure)
- commands (external API calls)
- domain (business logic)
- web (templating)
- controller (presentation)
Enforcement:
- Package structure enforces layer separation
- Spring dependency injection prevents circular dependencies
- Code reviews check for violations
- Architecture Overview - High-level architecture
- Spring Boot Architecture - Spring Boot integration
- Controller Architecture - Controller patterns
- Caching Architecture - Cache implementation
Next Steps: Explore specific packages in the codebase to understand implementation details.