diff --git a/documents/ADRs/adr-008-request-lifecycle-interceptor-pattern.md b/documents/ADRs/adr-008-request-lifecycle-interceptor-pattern.md new file mode 100644 index 000000000..b2a383122 --- /dev/null +++ b/documents/ADRs/adr-008-request-lifecycle-interceptor-pattern.md @@ -0,0 +1,405 @@ +# ADR 008: Request Lifecycle Interceptor Pattern for Extensible Integration + +In the context of the OJP project, +facing the need to standardize how libraries and modules integrate with request lifecycle phases while avoiding tight coupling and enabling third-party extensibility, + +we decided for adopting the **Request Lifecycle Interceptor Pattern** using a Chain of Responsibility approach inspired by Servlet Filters, with ServiceLoader-based discovery +and neglected hard-coded integration of features like Circuit Breaker, Slow Query Segregation, and SQL Enhancement directly into `StatementServiceImpl`, + +to achieve a pluggable architecture where external providers can create interceptors that hook into various phases of request processing (PRE_REQUEST, PRE_EXECUTION, RESOURCE_ACQUISITION, EXECUTION, POST_EXECUTION, RESOURCE_RELEASE, POST_REQUEST, EXCEPTION_HANDLING), +accepting the complexity of chain management and phase coordination, + +because this pattern enables powerful extensibility, reduces coupling in the core request handling code, allows third-party providers to add functionality without modifying OJP, and follows the established ServiceLoader pattern already used successfully for ConnectionPoolProvider and XAConnectionPoolProvider. + +## Context + +OJP Server acts as a proxy between applications and databases, processing SQL queries and updates through `StatementServiceImpl`. Currently, three major features are tightly integrated into the request processing flow: + +1. **CircuitBreaker** - Prevents repeatedly executing failing queries + - Integrated via direct method calls (`preCheck`, `onSuccess`, `onFailure`) + - Hard-coded at specific points in `executeQuery()` and `executeUpdate()` + +2. **SlowQuerySegregationManager** - Prevents connection starvation from slow queries + - Integrated via wrapper pattern (`executeWithSegregation()`) + - Per-datasource instances managed in a `ConcurrentHashMap` + +3. **SqlEnhancerEngine** (Apache Calcite) - Optimizes and transforms SQL + - Integrated via direct method call in `executeQueryInternal()` + - Enabled/disabled via configuration flag + +### Problems with Current Approach + +1. **Tight Coupling**: Features are hard-coded into `StatementServiceImpl`, which is already 2,528 lines +2. **Difficult Extension**: Adding new features requires modifying core classes +3. **No Standardization**: Each feature integrates differently (method calls vs wrappers vs flags) +4. **No Third-Party Support**: External providers cannot add lifecycle hooks +5. **Testing Complexity**: Features cannot be easily tested in isolation +6. **Maintenance Burden**: Changes to features require touching core request handling code + +### Requirements + +External providers should be able to: + +1. Hook into various phases of request processing +2. Transform SQL before execution +3. Control whether to proceed or short-circuit the chain +4. Monitor performance and record metrics +5. Handle failures and implement retry logic +6. Acquire and release resources around execution +7. Work seamlessly with other interceptors in a chain + +## Decision + +We will implement a **Request Lifecycle Interceptor Pattern** using: + +1. **Chain of Responsibility**: Interceptors form a chain, each can pass control to the next +2. **Servlet Filter Model**: Inspired by `javax.servlet.Filter` API pattern +3. **ServiceLoader Discovery**: External implementations loaded automatically via SPI +4. **Phased Lifecycle**: Eight distinct phases where interceptors can hook +5. **Priority-Based Ordering**: Interceptors execute in priority order (highest first) + +### Core Components + +#### 1. RequestInterceptor Interface (SPI) + +```java +public interface RequestInterceptor { + String id(); + int getPriority(); + boolean isAvailable(); + boolean supportsRequestType(RequestType requestType); + boolean supportsPhase(LifecyclePhase phase); + void intercept(RequestContext context, InterceptorChain chain) throws Exception; +} +``` + +#### 2. Lifecycle Phases + +1. **PRE_REQUEST** - Session validation, cluster health, request enrichment +2. **PRE_EXECUTION** - SQL hash, circuit breaker check, SQL transformation +3. **RESOURCE_ACQUISITION** - Connection/slot acquisition +4. **EXECUTION** - Actual database execution +5. **POST_EXECUTION** - Result processing, metadata extraction +6. **RESOURCE_RELEASE** - Connection/slot release, cleanup +7. **POST_REQUEST** - Success/failure recording, metrics publishing +8. **EXCEPTION_HANDLING** - Exception transformation, failure recording + +#### 3. RequestContext + +Mutable context object flowing through the chain containing: +- Request type, SQL (original and current), session info +- Connection, result, exception +- Timing information, custom attributes +- Short-circuit flag + +#### 4. InterceptorChain + +Manages chain execution with `proceed(context)` method, allowing interceptors to: +- Execute logic before proceeding +- Call `chain.proceed(context)` to continue +- Execute logic after proceeding (in finally block) +- Short-circuit by not calling proceed + +#### 5. RequestInterceptorRegistry + +Discovers interceptors via ServiceLoader, manages registration, provides filtering by request type and phase, sorts by priority. + +### Integration Approach + +#### Before (Current): + +```java +public void executeUpdate(StatementRequest request, ...) { + circuitBreaker.preCheck(stmtHash); // Hard-coded + SlowQuerySegregationManager manager = getManager(connHash); // Hard-coded + OpResult result = manager.executeWithSegregation( // Hard-coded wrapper + stmtHash, () -> executeUpdateInternal(request) + ); + circuitBreaker.onSuccess(stmtHash); // Hard-coded +} +``` + +#### After (With Interceptors): + +```java +public void executeUpdate(StatementRequest request, ...) { + RequestContext context = RequestContext.builder() + .requestType(RequestType.UPDATE) + .originalSql(request.getSql()) + .build(); + + InterceptorChainExecutor.execute(context, ctx -> { + OpResult result = executeUpdateInternal(request, ctx); + ctx.setResult(result); + }); +} +``` + +All cross-cutting concerns move to interceptors, core code becomes cleaner. + +### Migration Strategy + +1. **Phase 1**: Implement interceptor infrastructure (no behavior change) +2. **Phase 2**: Implement interceptor versions of existing features +3. **Phase 3**: Run old and new code in parallel with feature flags +4. **Phase 4**: Switch from old to new gradually +5. **Phase 5**: Remove legacy implementations + +Full backward compatibility maintained during migration. + +## Consequences + +### Positive + +1. **Pluggable Architecture**: Features completely decoupled from core request handling +2. **Third-Party Extensibility**: External providers can add interceptors via JAR in `ojp-libs` +3. **Standardized Integration**: Single, well-defined pattern for all lifecycle hooks +4. **Better Testability**: Each interceptor can be tested independently +5. **Reduced Complexity**: `StatementServiceImpl` becomes much simpler (target < 1000 lines) +6. **Composability**: Multiple interceptors work seamlessly together +7. **Clear Lifecycle**: Eight distinct phases make integration points explicit +8. **Familiar Pattern**: Servlet Filter model is well-understood by Java developers +9. **Consistent with SPIs**: Follows established pattern used for ConnectionPoolProvider +10. **No Recompilation**: Interceptors can be added/removed without rebuilding OJP + +### Negative + +1. **Implementation Complexity**: Chain management, phase coordination adds complexity +2. **Performance Overhead**: Each interceptor adds small constant time overhead (~0.05ms) +3. **Learning Curve**: Developers need to understand lifecycle phases and chain mechanics +4. **Debugging Complexity**: Tracing through interceptor chain may be harder than direct calls +5. **Migration Effort**: Existing features need to be refactored to interceptors +6. **Documentation Burden**: Comprehensive docs needed for third-party developers + +### Mitigations + +1. **Clear Documentation**: Comprehensive guide with examples (`Understanding-OJP-Interceptors.md`) +2. **Reference Implementations**: Built-in interceptors serve as examples +3. **Performance Testing**: Benchmark overhead, ensure < 1% impact +4. **Phased Migration**: Gradual rollout with feature flags +5. **Helpful Logging**: Log interceptor chain execution at DEBUG level +6. **Testing Support**: Provide test utilities for interceptor developers + +## Alternatives Considered + +### 1. Event-Driven Architecture + +**Approach**: Publish events at lifecycle points, interceptors subscribe via event bus + +**Pros**: +- True decoupling +- Asynchronous processing possible +- Dynamic subscription/unsubscription + +**Cons**: +- More complex infrastructure +- Harder to maintain execution order +- Difficult to handle exceptions consistently +- Performance overhead from event dispatch +- Harder to short-circuit request flow + +**Verdict**: **REJECTED** - Chain of Responsibility is simpler and more appropriate for synchronous request processing + +### 2. Aspect-Oriented Programming (AOP) + +**Approach**: Use AspectJ or Spring AOP to apply cross-cutting concerns + +**Pros**: +- Clean separation via aspects +- No code changes in core classes +- Mature tooling + +**Cons**: +- Requires AOP framework dependency (violates minimal dependencies principle) +- Compile-time or load-time weaving complexity +- Harder to debug (bytecode modification) +- Less explicit control flow +- Difficult for third-party providers to add aspects + +**Verdict**: **REJECTED** - SPI + Chain pattern is more explicit and doesn't require framework dependency + +### 3. Decorator Pattern + +**Approach**: Wrap `StatementServiceImpl` with decorator classes + +**Pros**: +- Classic OOP pattern +- Type-safe at compile time +- Easy to understand + +**Cons**: +- Requires compile-time composition +- Not discoverable at runtime via ServiceLoader +- Can't add decorators from external JARs easily +- Each decorator needs to implement entire service interface + +**Verdict**: **REJECTED** - Not flexible enough for runtime discovery and third-party extensions + +### 4. Plugin Framework (OSGi, JPMs) + +**Approach**: Use heavyweight plugin framework + +**Pros**: +- Full module isolation +- Sophisticated dependency management +- Hot-swapping capabilities + +**Cons**: +- Too heavyweight for the use case +- Adds significant complexity +- Steep learning curve +- Not consistent with OJP's lightweight approach + +**Verdict**: **REJECTED** - ServiceLoader provides sufficient modularity without the complexity + +### 5. Status Quo (Keep Current Approach) + +**Approach**: Continue hard-coding integrations into `StatementServiceImpl` + +**Pros**: +- No implementation effort +- No migration needed +- Simple and direct + +**Cons**: +- Cannot support third-party extensions +- `StatementServiceImpl` continues to grow +- Each feature integrates differently +- Tight coupling persists + +**Verdict**: **REJECTED** - Does not meet extensibility requirements + +## Implementation Plan + +### Timeline (12 weeks) + +- **Week 1-2**: Core infrastructure (interfaces, registry, chain) +- **Week 3-4**: Integration layer in `StatementServiceImpl` +- **Week 5-6**: Migrate CircuitBreaker to interceptor +- **Week 7**: Migrate SlowQuerySegregation to interceptor +- **Week 8**: Migrate SqlEnhancer to interceptor +- **Week 9-10**: Documentation, examples, testing +- **Week 11**: Beta testing with community +- **Week 12**: Final release + +### Success Criteria + +1. ✅ All existing features migrated to interceptors +2. ✅ Performance overhead < 1% (measured via benchmarks) +3. ✅ Third-party interceptors loadable from `ojp-libs` +4. ✅ `StatementServiceImpl` reduced to < 1000 lines +5. ✅ Complete documentation available +6. ✅ At least one community-contributed example + +### New Module Structure + +``` +ojp-interceptor-api/ + src/main/java/org/openjproxy/interceptor/ + RequestInterceptor.java + RequestContext.java + InterceptorChain.java + RequestType.java + LifecyclePhase.java + DataSourceMetadata.java + RequestInterceptorRegistry.java +``` + +Built-in interceptors will reside in `ojp-server`: + +``` +ojp-server/ + src/main/java/org/openjproxy/grpc/server/interceptor/ + CircuitBreakerInterceptor.java + SlowQueryInterceptor.java + SqlEnhancerInterceptor.java +``` + +## Related Decisions + +- **ADR-006**: Adopt SPI Pattern for Extensibility - This decision extends the SPI philosophy to request lifecycle +- **ADR-003**: Use HikariCP as Connection Pool - ConnectionPoolProvider SPI demonstrated ServiceLoader success +- **STATEMENTSERVICE_ACTION_PATTERN_MIGRATION**: Refactoring StatementServiceImpl - Interceptors further simplify this class + +## References + +- [Request Lifecycle Interceptor Pattern - Full Design](../designs/REQUEST_LIFECYCLE_INTERCEPTOR_PATTERN.md) +- [Understanding OJP SPIs](../Understanding-OJP-SPIs.md) +- [Java ServiceLoader Documentation](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) +- [Servlet Filter Specification](https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0.html#filters) +- [Chain of Responsibility Pattern](https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern) + +## Appendix: Example Interceptor + +```java +package com.example.ojp.interceptor; + +import org.openjproxy.interceptor.*; + +/** + * Example third-party query timeout interceptor. + * Demonstrates ServiceLoader-based integration. + */ +public class QueryTimeoutInterceptor implements RequestInterceptor { + + private final long timeoutMs; + + public QueryTimeoutInterceptor() { + this.timeoutMs = Long.parseLong( + System.getProperty("interceptor.timeout.ms", "30000") + ); + } + + @Override + public String id() { + return "query-timeout"; + } + + @Override + public int getPriority() { + return 100; + } + + @Override + public boolean supportsRequestType(RequestType requestType) { + return requestType == RequestType.QUERY; + } + + @Override + public boolean supportsPhase(LifecyclePhase phase) { + return phase == LifecyclePhase.EXECUTION; + } + + @Override + public void intercept(RequestContext context, InterceptorChain chain) + throws Exception { + // Set query timeout on connection + context.getConnection().ifPresent(conn -> { + try { + Statement stmt = conn.createStatement(); + stmt.setQueryTimeout((int) (timeoutMs / 1000)); + } catch (SQLException e) { + log.warn("Failed to set query timeout", e); + } + }); + + chain.proceed(context); + } +} +``` + +**META-INF/services/org.openjproxy.interceptor.RequestInterceptor**: +``` +com.example.ojp.interceptor.QueryTimeoutInterceptor +``` + +Deploy by placing JAR in `ojp-libs/` directory. No OJP recompilation needed. + +--- + +| Status | PROPOSED | +|---------------|------------------| +| Proposer(s) | OJP Architecture Team | +| Proposal date | 2026-02-01 | +| Approver(s) | Pending Review | +| Approval date | Pending | diff --git a/documents/analysis/README.md b/documents/analysis/README.md index 4cdaedf5b..188b1d426 100644 --- a/documents/analysis/README.md +++ b/documents/analysis/README.md @@ -2,7 +2,38 @@ This directory contains technical analysis documents for various OJP features and decisions. -## Latest Analysis (January 2026) +## Latest Analysis (February 2026) + +### 🆕 Request Lifecycle Interceptor Pattern + +**Question:** How can OJP standardize integration patterns for libraries like Circuit Breaker, Slow Query Segregation, and Apache Calcite, while enabling third-party extensibility? + +**Quick Answer:** YES - Adopt Request Lifecycle Interceptor Pattern using Chain of Responsibility and ServiceLoader. + +**Documents:** +- **Executive Summary**: [REQUEST_LIFECYCLE_INTERCEPTOR_SUMMARY.md](./REQUEST_LIFECYCLE_INTERCEPTOR_SUMMARY.md) - 10 min read + - Quick overview of the pattern + - Key benefits and use cases + - Example implementations + +- **Full Design**: [../designs/REQUEST_LIFECYCLE_INTERCEPTOR_PATTERN.md](../designs/REQUEST_LIFECYCLE_INTERCEPTOR_PATTERN.md) - 60 min read + - Complete technical specification + - Eight lifecycle phases defined + - Interface designs with full Javadoc + - Migration strategy (5 phases) + - Performance analysis and benchmarks + +- **Architectural Decision**: [../ADRs/adr-008-request-lifecycle-interceptor-pattern.md](../ADRs/adr-008-request-lifecycle-interceptor-pattern.md) - 20 min read + - Decision rationale + - Alternatives considered (Event-Driven, AOP, Decorator, OSGi) + - Trade-offs and consequences + - Implementation plan (12-week timeline) + +**Key Takeaway:** Inspired by Servlet Filters, this pattern enables powerful extensibility through a standardized Chain of Responsibility approach. External providers can create interceptors that hook into request lifecycle phases (PRE_REQUEST, PRE_EXECUTION, RESOURCE_ACQUISITION, EXECUTION, POST_EXECUTION, RESOURCE_RELEASE, POST_REQUEST, EXCEPTION_HANDLING) without modifying OJP core code. Interceptors are discovered via ServiceLoader and can be deployed by dropping JARs in `ojp-libs/` directory. + +--- + +## Previous Analysis (January 2026) ### 🆕 Agroal Connection Pool Evaluation @@ -53,6 +84,21 @@ This directory contains technical analysis documents for various OJP features an - [DRIVER_EXTERNALIZATION_IMPLEMENTATION_SUMMARY.md](./DRIVER_EXTERNALIZATION_IMPLEMENTATION_SUMMARY.md) - Driver externalization implementation +### Session Affinity + +- [SESSION_AFFINITY_ANALYSIS.md](./SESSION_AFFINITY_ANALYSIS.md) - Detailed session affinity analysis + +### SQL Enhancement + +- [CALCITE_QUERY_COMPLEXITY_FOR_SLOW_QUERY_SEGREGATION_ANALYSIS.md](./CALCITE_QUERY_COMPLEXITY_FOR_SLOW_QUERY_SEGREGATION_ANALYSIS.md) - Calcite query complexity analysis +- [sql_enhancer/](./sql_enhancer/) - SQL enhancer design documents + +### Extensibility & Integration Patterns + +- [REQUEST_LIFECYCLE_INTERCEPTOR_SUMMARY.md](./REQUEST_LIFECYCLE_INTERCEPTOR_SUMMARY.md) - Request lifecycle interceptor pattern summary +- [../designs/REQUEST_LIFECYCLE_INTERCEPTOR_PATTERN.md](../designs/REQUEST_LIFECYCLE_INTERCEPTOR_PATTERN.md) - Full interceptor pattern design +- [../ADRs/adr-008-request-lifecycle-interceptor-pattern.md](../ADRs/adr-008-request-lifecycle-interceptor-pattern.md) - ADR for interceptor pattern + --- ## How to Use These Documents @@ -94,5 +140,5 @@ When adding new analysis documents: --- -**Last Updated:** 2026-01-08 +**Last Updated:** 2026-02-01 **Maintained By:** OJP Core Team diff --git a/documents/analysis/REQUEST_LIFECYCLE_INTERCEPTOR_SUMMARY.md b/documents/analysis/REQUEST_LIFECYCLE_INTERCEPTOR_SUMMARY.md new file mode 100644 index 000000000..c73d28e82 --- /dev/null +++ b/documents/analysis/REQUEST_LIFECYCLE_INTERCEPTOR_SUMMARY.md @@ -0,0 +1,360 @@ +# Request Lifecycle Interceptor Pattern - Executive Summary + +## Overview + +A new integration pattern for OJP that standardizes how libraries and modules interact with request lifecycle phases, inspired by Servlet Filters and implementing a Chain of Responsibility pattern with ServiceLoader-based discovery. + +## The Problem + +Currently, features like Circuit Breaker, Slow Query Segregation, and SQL Enhancement are **tightly coupled** to `StatementServiceImpl`: + +```java +// Current approach - hard-coded integrations +public void executeUpdate(StatementRequest request, ...) { + circuitBreaker.preCheck(stmtHash); // Hard-coded ❌ + manager = getSlowQueryManager(connHash); // Hard-coded ❌ + result = manager.executeWithSegregation(...); // Hard-coded wrapper ❌ + circuitBreaker.onSuccess(stmtHash); // Hard-coded ❌ +} +``` + +**Issues:** +- Can't add features without modifying core code +- Each feature integrates differently +- Third-party providers can't extend OJP +- Testing is complex +- `StatementServiceImpl` is 2,528 lines and growing + +## The Solution + +**Request Lifecycle Interceptor Pattern** - A standardized way to hook into request processing: + +```java +// New approach - clean and extensible +public void executeUpdate(StatementRequest request, ...) { + RequestContext context = RequestContext.builder() + .requestType(RequestType.UPDATE) + .originalSql(request.getSql()) + .build(); + + // All cross-cutting concerns handled by interceptors + InterceptorChainExecutor.execute(context, ctx -> { + OpResult result = executeUpdateInternal(request, ctx); + ctx.setResult(result); + }); +} +``` + +## Key Concepts + +### 1. Eight Lifecycle Phases + +Interceptors can hook into any phase: + +1. **PRE_REQUEST** - Session validation, request enrichment +2. **PRE_EXECUTION** - Circuit breaker, SQL transformation +3. **RESOURCE_ACQUISITION** - Connection/slot acquisition +4. **EXECUTION** - Database execution +5. **POST_EXECUTION** - Result processing +6. **RESOURCE_RELEASE** - Cleanup +7. **POST_REQUEST** - Metrics, logging +8. **EXCEPTION_HANDLING** - Error handling + +### 2. RequestInterceptor Interface + +```java +public interface RequestInterceptor { + String id(); + int getPriority(); // Higher = runs first + void intercept(RequestContext context, InterceptorChain chain) throws Exception; +} +``` + +### 3. Chain of Responsibility + +Interceptors form a chain, each can: +- Execute logic before proceeding +- Call `chain.proceed(context)` to continue +- Execute logic after (in try-finally) +- Short-circuit by not calling proceed + +### 4. ServiceLoader Discovery + +Interceptors are discovered automatically: + +``` +META-INF/services/org.openjproxy.interceptor.RequestInterceptor +com.example.MyInterceptor +``` + +Drop JAR in `ojp-libs/` → Interceptor loads automatically ✅ + +## Example: Circuit Breaker Interceptor + +```java +public class CircuitBreakerInterceptor implements RequestInterceptor { + + @Override + public String id() { + return "circuit-breaker"; + } + + @Override + public int getPriority() { + return 300; // High priority + } + + @Override + public boolean supportsPhase(LifecyclePhase phase) { + return phase == PRE_EXECUTION || phase == POST_REQUEST; + } + + @Override + public void intercept(RequestContext context, InterceptorChain chain) + throws Exception { + + if (context.getCurrentPhase() == PRE_EXECUTION) { + // Check before execution + circuitBreaker.preCheck(context.getSqlHash()); + chain.proceed(context); + + } else if (context.getCurrentPhase() == POST_REQUEST) { + // Record success/failure + if (context.getException().isPresent()) { + circuitBreaker.onFailure(context.getSqlHash(), ...); + } else { + circuitBreaker.onSuccess(context.getSqlHash()); + } + chain.proceed(context); + } + } +} +``` + +## Example: Third-Party Query Logging Interceptor + +```java +package com.acme.ojp; + +public class QueryLoggerInterceptor implements RequestInterceptor { + + @Override + public String id() { + return "acme-query-logger"; + } + + @Override + public int getPriority() { + return 50; // Low priority, runs late + } + + @Override + public void intercept(RequestContext context, InterceptorChain chain) + throws Exception { + + if (context.getCurrentPhase() == POST_REQUEST) { + long duration = context.getEndTimeMillis().orElse(0L) + - context.getStartTimeMillis(); + + logger.info("Query executed: sql={}, duration={}ms, success={}", + context.getOriginalSql(), + duration, + !context.getException().isPresent()); + } + + chain.proceed(context); + } +} +``` + +**Deploy**: Just drop JAR in `ojp-libs/` - no OJP recompilation needed! 🚀 + +## Benefits + +### For OJP Core +- ✅ **Simpler code**: `StatementServiceImpl` reduces from 2,528 → <1,000 lines +- ✅ **Better testability**: Each interceptor tested independently +- ✅ **Easier maintenance**: Features don't touch core code +- ✅ **Clear separation**: Business logic separate from cross-cutting concerns + +### For OJP Users +- ✅ **Extensibility**: Add custom interceptors without modifying OJP +- ✅ **Flexibility**: Enable/disable interceptors via configuration +- ✅ **Composability**: Multiple interceptors work together seamlessly +- ✅ **Familiar pattern**: Servlet Filter model is well-understood + +### For Third-Party Providers +- ✅ **Easy integration**: Implement interface, drop in `ojp-libs/` +- ✅ **No recompilation**: OJP doesn't need to be rebuilt +- ✅ **Full control**: Access entire request lifecycle +- ✅ **Rich context**: All request information available + +## Request Flow Example + +``` +Client Request + ↓ +┌────────────────────────────────────────────────┐ +│ PRE_REQUEST Phase │ +│ • Authentication Interceptor (priority: 1000) │ +│ • Rate Limiting Interceptor (priority: 900) │ +└────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────┐ +│ PRE_EXECUTION Phase │ +│ • SQL Enhancer Interceptor (priority: 500) │ +│ [SQL: SELECT * FROM users] │ +│ [Enhanced: SELECT id, name FROM users] │ +│ • Circuit Breaker Interceptor (priority: 300) │ +│ [Checks if query is failing repeatedly] │ +└────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────┐ +│ RESOURCE_ACQUISITION Phase │ +│ • Slow Query Interceptor (priority: 200) │ +│ [Acquires appropriate slot] │ +└────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────┐ +│ EXECUTION Phase │ +│ • Core OJP Logic │ +│ [Actual database execution] │ +└────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────┐ +│ POST_EXECUTION Phase │ +│ • Result Cache Interceptor (priority: 150) │ +│ [Caches result for future queries] │ +└────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────┐ +│ RESOURCE_RELEASE Phase │ +│ • Slow Query Interceptor (priority: 200) │ +│ [Releases slot] │ +└────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────┐ +│ POST_REQUEST Phase │ +│ • Metrics Interceptor (priority: 100) │ +│ [Records timing metrics] │ +│ • Circuit Breaker Interceptor (priority: 300) │ +│ [Records success] │ +│ • Query Logger Interceptor (priority: 50) │ +│ [Logs query execution] │ +└────────────────────────────────────────────────┘ + ↓ +Response to Client +``` + +## Performance + +**Overhead Analysis:** +- No interceptors: < 0.01ms +- 3 interceptors (typical): 0.05-0.1ms +- 10 interceptors: 0.2-0.3ms + +**Compared to typical query execution (10-1000ms): Negligible ✅** + +## Migration Path + +### Phase 1: Add Infrastructure +- Implement interfaces +- Add registry +- **No behavior change** + +### Phase 2: Create Interceptors +- CircuitBreakerInterceptor +- SlowQueryInterceptor +- SqlEnhancerInterceptor + +### Phase 3: Parallel Execution +- Run old and new code together +- Feature flags control which is active + +### Phase 4: Switch Over +- Gradually enable interceptors +- Disable legacy code + +### Phase 5: Cleanup +- Remove old implementations +- **Fully migrated ✅** + +## Configuration + +```properties +# Enable interceptor system +interceptor.enabled=true + +# Circuit Breaker +interceptor.circuit-breaker.enabled=true +interceptor.circuit-breaker.priority=300 +interceptor.circuit-breaker.failure-threshold=3 + +# Slow Query +interceptor.slow-query.enabled=true +interceptor.slow-query.priority=200 + +# SQL Enhancer +interceptor.sql-enhancer.enabled=true +interceptor.sql-enhancer.priority=500 +``` + +## Success Criteria + +1. ✅ All existing features migrated to interceptors +2. ✅ Performance overhead < 1% +3. ✅ Third-party interceptors loadable from `ojp-libs` +4. ✅ `StatementServiceImpl` < 1,000 lines +5. ✅ Complete documentation available +6. ✅ Community-contributed example exists + +## Timeline + +- **Week 1-2**: Core infrastructure +- **Week 3-4**: Integration layer +- **Week 5-8**: Migrate existing features +- **Week 9-10**: Documentation & examples +- **Week 11-12**: Testing & release + +## Comparison with Servlet Filters + +| Aspect | Servlet Filter | OJP Interceptor | +|--------|---------------|-----------------| +| Pattern | Chain of Responsibility | Chain of Responsibility | +| Discovery | web.xml or @WebFilter | ServiceLoader | +| Method | `doFilter(request, response, chain)` | `intercept(context, chain)` | +| Proceed | `chain.doFilter(request, response)` | `chain.proceed(context)` | +| Context | ServletRequest/Response | RequestContext | +| Phases | Single (filter) | Eight (lifecycle) | + +**Key Insight**: OJP interceptors are like Servlet Filters, but with **multiple lifecycle phases** for finer-grained control. + +## Related Documents + +- 📄 [Full Design Document](REQUEST_LIFECYCLE_INTERCEPTOR_PATTERN.md) - Complete technical specification +- 📄 [ADR-008](../ADRs/adr-008-request-lifecycle-interceptor-pattern.md) - Architectural decision record +- 📄 [Understanding OJP SPIs](../Understanding-OJP-SPIs.md) - Existing SPI documentation + +## Questions? + +**Q: Will this break existing code?** +A: No, fully backward compatible during migration. + +**Q: What's the performance impact?** +A: < 1% overhead, negligible compared to database execution time. + +**Q: Can I add my own interceptor?** +A: Yes! Implement `RequestInterceptor`, add to JAR, drop in `ojp-libs/`. + +**Q: How is this different from events?** +A: Chain pattern is synchronous, ordered, and can short-circuit. Better for request processing. + +**Q: Do I need to rebuild OJP?** +A: No, interceptors are loaded dynamically via ServiceLoader. + +--- + +**Status**: DRAFT - PENDING REVIEW +**Version**: 1.0 +**Date**: 2026-02-01 +**Contact**: OJP Architecture Team diff --git a/documents/designs/REQUEST_LIFECYCLE_INTERCEPTOR_PATTERN.md b/documents/designs/REQUEST_LIFECYCLE_INTERCEPTOR_PATTERN.md new file mode 100644 index 000000000..15ec663d3 --- /dev/null +++ b/documents/designs/REQUEST_LIFECYCLE_INTERCEPTOR_PATTERN.md @@ -0,0 +1,1412 @@ +# Request Lifecycle Interceptor Pattern for OJP + +## Executive Summary + +This document analyzes a new integration pattern for OJP that standardizes how libraries and modules interact with the request lifecycle. The pattern is inspired by Servlet Filters and implements a Chain of Responsibility approach, allowing external providers to create interceptors that can be loaded via ServiceLoader. + +## Current State Analysis + +### Existing Integration Points + +Currently, OJP has three main libraries/modules that integrate directly with the request execution flow: + +1. **CircuitBreaker** - Integrated directly in `StatementServiceImpl` + - Called at: pre-execution (`preCheck`), post-success (`onSuccess`), post-failure (`onFailure`) + - Current integration: Hard-coded method calls in `executeQuery()` and `executeUpdate()` + +2. **SlowQuerySegregationManager** - Integrated directly in `StatementServiceImpl` + - Called at: wraps the entire execution with `executeWithSegregation()` + - Current integration: Hard-coded wrapper around query/update execution + - Per-datasource instances managed in a `ConcurrentHashMap` + +3. **SqlEnhancerEngine** (Apache Calcite) - Integrated directly in query execution + - Called at: pre-execution phase (SQL transformation) + - Current integration: Direct method call in `executeQueryInternal()` + - Enabled/disabled via configuration flag + +### Current Request Lifecycle Flow + +``` +executeQuery/executeUpdate (public API) + ↓ +1. updateSessionActivity() +2. hashSqlQuery() +3. processClusterHealth() +4. circuitBreaker.preCheck() ← Integration Point 1 +5. getSlowQuerySegregationManager() +6. manager.executeWithSegregation() ← Integration Point 2 + ↓ + executeQueryInternal/executeUpdateInternal + ↓ + 7. sessionConnection() + 8. sqlEnhancerEngine.enhance() ← Integration Point 3 + 9. Statement creation & execution + 10. Result processing + ↓ +11. circuitBreaker.onSuccess() ← Integration Point 4 + OR + circuitBreaker.onFailure() ← Integration Point 5 +``` + +### Existing SPI Pattern + +OJP already uses the ServiceLoader pattern successfully for: + +- **ConnectionPoolProvider** - Standard connection pools (HikariCP, DBCP) +- **XAConnectionPoolProvider** - XA transaction pools +- **JDBC Drivers** - Database drivers loaded from `ojp-libs` directory + +This demonstrates OJP's commitment to extensibility and provides a proven pattern to follow. + +## Problem Statement + +### Current Limitations + +1. **Tight Coupling**: Features like CircuitBreaker, SlowQuerySegregation, and SqlEnhancer are directly coupled to `StatementServiceImpl` +2. **Hard to Extend**: Adding new features requires modifying core classes +3. **No Standardization**: Each feature integrates differently (method calls, wrappers, flags) +4. **No Third-Party Support**: External providers cannot add their own lifecycle hooks +5. **Testing Complexity**: Difficult to test features in isolation +6. **Maintenance Burden**: `StatementServiceImpl` is already a 2,528-line class + +### Desired Capabilities + +External providers should be able to: + +1. **Intercept Requests**: Hook into various phases of request processing +2. **Transform SQL**: Modify SQL before execution (like Calcite does) +3. **Control Execution**: Decide whether to proceed or short-circuit +4. **Monitor Performance**: Track timing and metrics +5. **Handle Failures**: React to exceptions and implement retry logic +6. **Manage Resources**: Acquire/release resources around execution +7. **Chain Together**: Multiple interceptors working in sequence + +## Proposed Solution: Request Lifecycle Interceptor Pattern + +### Design Principles + +1. **Chain of Responsibility**: Interceptors form a chain, each can pass control to the next +2. **Servlet Filter Model**: Inspired by the well-understood `javax.servlet.Filter` pattern +3. **ServiceLoader Discovery**: External implementations loaded automatically +4. **Backward Compatible**: Existing features can be migrated gradually +5. **Minimal Overhead**: Negligible performance impact when interceptors are not present + +### Request Lifecycle Phases + +The request lifecycle is divided into distinct phases where interceptors can hook: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ REQUEST RECEIVED │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Phase 1: PRE_REQUEST │ +│ - Session validation │ +│ - Cluster health processing │ +│ - Request enrichment │ +│ Examples: Authentication, Rate Limiting, Logging │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Phase 2: PRE_EXECUTION │ +│ - SQL hash generation │ +│ - Circuit breaker pre-check │ +│ - SQL transformation/optimization │ +│ Examples: Circuit Breaker, SQL Rewriting, Query Validation │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Phase 3: RESOURCE_ACQUISITION │ +│ - Slow query slot acquisition │ +│ - Connection acquisition │ +│ - Transaction management │ +│ Examples: Slow Query Segregation, Connection Pooling │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Phase 4: EXECUTION │ +│ - Statement preparation │ +│ - Parameter binding │ +│ - Actual database execution │ +│ Examples: Query Execution Monitoring, Execution Tracing │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Phase 5: POST_EXECUTION │ +│ - Result processing │ +│ - Metadata extraction │ +│ - Performance recording │ +│ Examples: Result Caching, Performance Metrics │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Phase 6: RESOURCE_RELEASE │ +│ - Connection release │ +│ - Slot release │ +│ - Cleanup │ +│ Examples: Resource Tracking, Leak Detection │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Phase 7: POST_REQUEST │ +│ - Success/failure recording │ +│ - Metrics publishing │ +│ - Logging │ +│ Examples: Audit Logging, Circuit Breaker State Update │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ RESPONSE SENT │ +└─────────────────────────────────────────────────────────────┘ + +Exception Flow: +┌─────────────────────────────────────────────────────────────┐ +│ Phase X: EXCEPTION_HANDLING │ +│ - Exception transformation │ +│ - Failure recording │ +│ - Recovery attempts │ +│ Examples: Circuit Breaker Failure Recording, Retry Logic │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Interface Design + +### Core Interfaces + +#### 1. RequestInterceptor - Main Interceptor Interface + +```java +package org.openjproxy.interceptor; + +/** + * Service Provider Interface (SPI) for request lifecycle interceptors. + * + *

Interceptors can hook into various phases of request processing to provide + * cross-cutting concerns like monitoring, transformation, circuit breaking, + * and resource management. This pattern is inspired by Servlet Filters and + * implements a Chain of Responsibility approach.

+ * + *

Implementations should be registered via the standard Java + * {@link java.util.ServiceLoader} mechanism by creating a file named + * {@code META-INF/services/org.openjproxy.interceptor.RequestInterceptor} + * containing the fully qualified class name of the implementation.

+ * + *

Interceptors are invoked in priority order (highest first) and can: + *

+ * + * @see RequestContext + * @see InterceptorChain + */ +public interface RequestInterceptor { + + /** + * Returns the unique identifier for this interceptor. + * + * @return the interceptor ID, never null or empty + */ + String id(); + + /** + * Returns the priority of this interceptor for ordering. + * Higher values indicate higher priority (executed first). + * + *

Recommended ranges: + *

+ * + * @return the interceptor priority (default: 0) + */ + default int getPriority() { + return 0; + } + + /** + * Checks if this interceptor is available and should be used. + * + * @return true if available, false otherwise + */ + default boolean isAvailable() { + return true; + } + + /** + * Checks if this interceptor supports the given request type. + * + * @param requestType the type of request (QUERY, UPDATE, TRANSACTION, etc.) + * @return true if this interceptor should handle this request type + */ + default boolean supportsRequestType(RequestType requestType) { + return true; // By default, support all types + } + + /** + * Checks if this interceptor should run for the given lifecycle phase. + * + * @param phase the lifecycle phase + * @return true if this interceptor should run in this phase + */ + default boolean supportsPhase(LifecyclePhase phase) { + return true; // By default, support all phases + } + + /** + * Intercepts the request processing at various lifecycle phases. + * + *

Implementations must call {@code chain.proceed(context)} to continue + * the chain, or can choose to short-circuit by not calling it.

+ * + *

Example implementation: + *

{@code
+     * public void intercept(RequestContext context, InterceptorChain chain) 
+     *         throws Exception {
+     *     // Pre-processing
+     *     long start = System.currentTimeMillis();
+     *     
+     *     try {
+     *         // Proceed with the chain
+     *         chain.proceed(context);
+     *         
+     *         // Post-processing on success
+     *         long duration = System.currentTimeMillis() - start;
+     *         recordSuccess(context, duration);
+     *     } catch (Exception e) {
+     *         // Handle failure
+     *         recordFailure(context, e);
+     *         throw e; // Re-throw to propagate
+     *     } finally {
+     *         // Cleanup
+     *         releaseResources();
+     *     }
+     * }
+     * }
+ * + * @param context the request context containing all request information + * @param chain the interceptor chain to continue processing + * @throws Exception if an error occurs during interception + */ + void intercept(RequestContext context, InterceptorChain chain) throws Exception; +} +``` + +#### 2. RequestContext - Contextual Information + +```java +package org.openjproxy.interceptor; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.util.Map; +import java.util.Optional; + +/** + * Context object that flows through the interceptor chain, containing all + * information about the current request and its execution state. + * + *

The context is mutable and can be modified by interceptors. Changes + * made to the context will be visible to subsequent interceptors in the chain.

+ */ +public interface RequestContext { + + /** + * Returns the type of request being processed. + */ + RequestType getRequestType(); + + /** + * Returns the current lifecycle phase. + */ + LifecyclePhase getCurrentPhase(); + + /** + * Returns the original SQL statement. + */ + String getOriginalSql(); + + /** + * Returns the current SQL statement (may have been transformed by interceptors). + */ + String getCurrentSql(); + + /** + * Sets the SQL statement (allows transformation). + */ + void setCurrentSql(String sql); + + /** + * Returns the SQL statement hash for tracking. + */ + String getSqlHash(); + + /** + * Returns the session information. + */ + SessionInfo getSessionInfo(); + + /** + * Returns the connection hash identifying the datasource. + */ + String getConnectionHash(); + + /** + * Returns request parameters (if applicable). + */ + Optional> getParameters(); + + /** + * Returns the database connection (available during and after EXECUTION phase). + */ + Optional getConnection(); + + /** + * Sets the database connection. + */ + void setConnection(Connection connection); + + /** + * Returns the execution result (available during POST_EXECUTION phase). + */ + Optional getResult(); + + /** + * Sets the execution result. + */ + void setResult(Object result); + + /** + * Returns the result set (for queries, available during POST_EXECUTION phase). + */ + Optional getResultSet(); + + /** + * Returns the exception if one occurred (available during EXCEPTION_HANDLING phase). + */ + Optional getException(); + + /** + * Sets the exception (allows transformation). + */ + void setException(Exception exception); + + /** + * Returns execution start time in milliseconds. + */ + long getStartTimeMillis(); + + /** + * Returns execution end time in milliseconds (available after execution). + */ + Optional getEndTimeMillis(); + + /** + * Gets a custom attribute by key. + * Interceptors can use this to pass information between each other. + */ + Object getAttribute(String key); + + /** + * Sets a custom attribute. + */ + void setAttribute(String key, Object value); + + /** + * Checks if the request has been short-circuited. + */ + boolean isShortCircuited(); + + /** + * Marks the request as short-circuited (stops the chain). + */ + void setShortCircuited(boolean shortCircuited); + + /** + * Returns metadata about the target datasource. + */ + DataSourceMetadata getDataSourceMetadata(); +} +``` + +#### 3. InterceptorChain - Chain Management + +```java +package org.openjproxy.interceptor; + +/** + * Represents the chain of interceptors to be executed. + * + *

Interceptors must call {@code proceed(context)} to continue + * the chain. If they don't call proceed, the chain is short-circuited.

+ */ +public interface InterceptorChain { + + /** + * Proceeds to the next interceptor in the chain. + * + *

If this is the last interceptor, proceeds to actual request execution. + * If the context is marked as short-circuited, returns immediately.

+ * + * @param context the request context + * @throws Exception if an error occurs during processing + */ + void proceed(RequestContext context) throws Exception; + + /** + * Returns whether there are more interceptors in the chain. + */ + boolean hasNext(); + + /** + * Returns the index of the current interceptor. + */ + int getCurrentIndex(); + + /** + * Returns the total number of interceptors in the chain. + */ + int getTotalCount(); +} +``` + +#### 4. Supporting Enums + +```java +package org.openjproxy.interceptor; + +/** + * Defines the type of request being processed. + */ +public enum RequestType { + /** SQL query execution (SELECT) */ + QUERY, + + /** SQL update execution (INSERT, UPDATE, DELETE) */ + UPDATE, + + /** Batch operation */ + BATCH, + + /** Stored procedure call */ + CALLABLE, + + /** Transaction management (commit, rollback) */ + TRANSACTION, + + /** XA distributed transaction operation */ + XA_OPERATION, + + /** Connection management */ + CONNECTION, + + /** Result set fetch operation */ + RESULT_SET_FETCH, + + /** LOB (Large Object) operation */ + LOB_OPERATION +} + +/** + * Defines the lifecycle phases where interceptors can hook. + */ +public enum LifecyclePhase { + /** Before request processing begins */ + PRE_REQUEST, + + /** Before execution (after SQL hash, before circuit breaker) */ + PRE_EXECUTION, + + /** During resource acquisition (connections, slots) */ + RESOURCE_ACQUISITION, + + /** During actual database execution */ + EXECUTION, + + /** After execution completes successfully */ + POST_EXECUTION, + + /** During resource cleanup and release */ + RESOURCE_RELEASE, + + /** After request completes (success or failure) */ + POST_REQUEST, + + /** When an exception occurs at any phase */ + EXCEPTION_HANDLING +} + +/** + * Metadata about the target datasource. + */ +public interface DataSourceMetadata { + String getConnectionHash(); + String getDatabaseType(); + String getUrl(); + boolean isXAEnabled(); + Map getPoolStatistics(); +} +``` + +### Registry and Loading + +```java +package org.openjproxy.interceptor; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Registry for discovering and managing RequestInterceptor implementations. + * + *

Uses Java's ServiceLoader mechanism to automatically discover interceptors + * on the classpath and in the external ojp-libs directory.

+ */ +public final class RequestInterceptorRegistry { + + private static final Map interceptors = new ConcurrentHashMap<>(); + private static volatile boolean initialized = false; + private static final Object initLock = new Object(); + + private RequestInterceptorRegistry() { + // Utility class + } + + /** + * Discovers and registers all available RequestInterceptor implementations. + */ + public static void initialize() { + if (!initialized) { + synchronized (initLock) { + if (!initialized) { + loadInterceptors(); + initialized = true; + } + } + } + } + + private static void loadInterceptors() { + ServiceLoader loader = ServiceLoader.load(RequestInterceptor.class); + + for (RequestInterceptor interceptor : loader) { + try { + if (interceptor.isAvailable()) { + interceptors.put(interceptor.id(), interceptor); + log.info("Registered RequestInterceptor: {} (priority: {})", + interceptor.id(), interceptor.getPriority()); + } + } catch (Exception e) { + log.error("Failed to register interceptor: {}", + interceptor.getClass().getName(), e); + } + } + + log.info("Loaded {} request interceptors", interceptors.size()); + } + + /** + * Gets all registered interceptors sorted by priority (highest first). + */ + public static List getInterceptors() { + initialize(); + return interceptors.values().stream() + .sorted(Comparator.comparingInt(RequestInterceptor::getPriority).reversed()) + .collect(Collectors.toList()); + } + + /** + * Gets interceptors filtered by request type and phase, sorted by priority. + */ + public static List getInterceptors(RequestType requestType, + LifecyclePhase phase) { + initialize(); + return interceptors.values().stream() + .filter(i -> i.supportsRequestType(requestType)) + .filter(i -> i.supportsPhase(phase)) + .sorted(Comparator.comparingInt(RequestInterceptor::getPriority).reversed()) + .collect(Collectors.toList()); + } + + /** + * Gets a specific interceptor by ID. + */ + public static Optional getInterceptor(String id) { + initialize(); + return Optional.ofNullable(interceptors.get(id)); + } +} +``` + +## Implementation Strategy + +### Phase 1: Core Infrastructure (Week 1-2) + +1. Create new module: `ojp-interceptor-api` + - Define all interfaces + - Create default implementations + - Add to Maven build + +2. Implement `RequestContext` and `InterceptorChain` + - Mutable context object + - Chain implementation with proceed() logic + - Phase tracking + +3. Implement `RequestInterceptorRegistry` + - ServiceLoader integration + - Priority-based sorting + - Filtering by type and phase + +### Phase 2: Integration Layer (Week 3-4) + +1. Add interceptor invocation to `StatementServiceImpl` + - Wrap existing code with interceptor calls + - Invoke at each lifecycle phase + - Maintain backward compatibility + +2. Create `InterceptorChainExecutor` + - Handles chain execution + - Exception propagation + - Short-circuit logic + +### Phase 3: Migrate Existing Features (Week 5-8) + +1. **CircuitBreaker** → CircuitBreakerInterceptor + - Priority: 300 + - Phases: PRE_EXECUTION, POST_REQUEST, EXCEPTION_HANDLING + - Supports: QUERY, UPDATE + +2. **SlowQuerySegregationManager** → SlowQueryInterceptor + - Priority: 200 + - Phases: RESOURCE_ACQUISITION, RESOURCE_RELEASE + - Supports: QUERY, UPDATE + +3. **SqlEnhancerEngine** → SqlEnhancerInterceptor + - Priority: 500 + - Phases: PRE_EXECUTION + - Supports: QUERY + +### Phase 4: Documentation and Examples (Week 9-10) + +1. Create comprehensive documentation +2. Provide example interceptors +3. Write migration guide +4. Update Understanding-OJP-SPIs.md + +## Example Implementations + +### Example 1: Circuit Breaker Interceptor + +```java +package org.openjproxy.interceptor.builtin; + +import org.openjproxy.interceptor.*; + +public class CircuitBreakerInterceptor implements RequestInterceptor { + + private final CircuitBreaker circuitBreaker; + + public CircuitBreakerInterceptor() { + // Load from configuration + this.circuitBreaker = new CircuitBreaker(5000, 3); + } + + @Override + public String id() { + return "circuit-breaker"; + } + + @Override + public int getPriority() { + return 300; // Resource management priority + } + + @Override + public boolean supportsRequestType(RequestType requestType) { + return requestType == RequestType.QUERY || requestType == RequestType.UPDATE; + } + + @Override + public boolean supportsPhase(LifecyclePhase phase) { + return phase == LifecyclePhase.PRE_EXECUTION + || phase == LifecyclePhase.POST_REQUEST + || phase == LifecyclePhase.EXCEPTION_HANDLING; + } + + @Override + public void intercept(RequestContext context, InterceptorChain chain) throws Exception { + String sqlHash = context.getSqlHash(); + + if (context.getCurrentPhase() == LifecyclePhase.PRE_EXECUTION) { + // Check if circuit is open before execution + circuitBreaker.preCheck(sqlHash); + chain.proceed(context); + + } else if (context.getCurrentPhase() == LifecyclePhase.POST_REQUEST) { + // Record success + if (!context.getException().isPresent()) { + circuitBreaker.onSuccess(sqlHash); + } + chain.proceed(context); + + } else if (context.getCurrentPhase() == LifecyclePhase.EXCEPTION_HANDLING) { + // Record failure + context.getException().ifPresent(e -> { + if (e instanceof SQLException) { + circuitBreaker.onFailure(sqlHash, (SQLException) e); + } + }); + chain.proceed(context); + } + } +} +``` + +### Example 2: Slow Query Segregation Interceptor + +```java +package org.openjproxy.interceptor.builtin; + +import org.openjproxy.interceptor.*; + +public class SlowQueryInterceptor implements RequestInterceptor { + + private final Map managers = new ConcurrentHashMap<>(); + + @Override + public String id() { + return "slow-query-segregation"; + } + + @Override + public int getPriority() { + return 200; // Resource management, lower than circuit breaker + } + + @Override + public boolean supportsRequestType(RequestType requestType) { + return requestType == RequestType.QUERY || requestType == RequestType.UPDATE; + } + + @Override + public boolean supportsPhase(LifecyclePhase phase) { + return phase == LifecyclePhase.RESOURCE_ACQUISITION + || phase == LifecyclePhase.RESOURCE_RELEASE; + } + + @Override + public void intercept(RequestContext context, InterceptorChain chain) throws Exception { + String connHash = context.getConnectionHash(); + String sqlHash = context.getSqlHash(); + SlowQuerySegregationManager manager = getOrCreateManager(connHash); + + if (context.getCurrentPhase() == LifecyclePhase.RESOURCE_ACQUISITION) { + // Store slot acquisition state for cleanup + boolean isSlowOp = manager.isSlowOperation(sqlHash); + context.setAttribute("slowQuerySlot.isSlowOp", isSlowOp); + + if (isSlowOp) { + boolean acquired = manager.acquireSlowSlot(); + context.setAttribute("slowQuerySlot.acquired", acquired); + } else { + boolean acquired = manager.acquireFastSlot(); + context.setAttribute("slowQuerySlot.acquired", acquired); + } + + chain.proceed(context); + + } else if (context.getCurrentPhase() == LifecyclePhase.RESOURCE_RELEASE) { + // Release the slot + Boolean isSlowOp = (Boolean) context.getAttribute("slowQuerySlot.isSlowOp"); + Boolean acquired = (Boolean) context.getAttribute("slowQuerySlot.acquired"); + + if (Boolean.TRUE.equals(acquired)) { + if (Boolean.TRUE.equals(isSlowOp)) { + manager.releaseSlowSlot(); + } else { + manager.releaseFastSlot(); + } + } + + chain.proceed(context); + } + } + + private SlowQuerySegregationManager getOrCreateManager(String connHash) { + return managers.computeIfAbsent(connHash, k -> + new SlowQuerySegregationManager(/* config */)); + } +} +``` + +### Example 3: SQL Enhancement Interceptor + +```java +package org.openjproxy.interceptor.builtin; + +import org.openjproxy.interceptor.*; + +public class SqlEnhancerInterceptor implements RequestInterceptor { + + private final SqlEnhancerEngine enhancerEngine; + + public SqlEnhancerInterceptor() { + // Load configuration + this.enhancerEngine = new SqlEnhancerEngine(/* config */); + } + + @Override + public String id() { + return "sql-enhancer"; + } + + @Override + public int getPriority() { + return 500; // High priority for SQL transformation + } + + @Override + public boolean supportsRequestType(RequestType requestType) { + return requestType == RequestType.QUERY; + } + + @Override + public boolean supportsPhase(LifecyclePhase phase) { + return phase == LifecyclePhase.PRE_EXECUTION; + } + + @Override + public void intercept(RequestContext context, InterceptorChain chain) throws Exception { + if (!enhancerEngine.isEnabled()) { + chain.proceed(context); + return; + } + + String originalSql = context.getCurrentSql(); + + // Enhance the SQL + SqlEnhancementResult result = enhancerEngine.enhance(originalSql); + + if (result.wasModified()) { + // Transform the SQL + context.setCurrentSql(result.getEnhancedSql()); + context.setAttribute("sql.enhanced", true); + context.setAttribute("sql.enhancementTime", result.getProcessingTimeMs()); + } + + chain.proceed(context); + } +} +``` + +### Example 4: Custom Audit Logging Interceptor (Third-Party) + +```java +package com.example.ojp.interceptor; + +import org.openjproxy.interceptor.*; + +/** + * Example third-party interceptor for audit logging. + * Demonstrates how external providers can create custom interceptors. + */ +public class AuditLoggingInterceptor implements RequestInterceptor { + + private final AuditLogger auditLogger; + + public AuditLoggingInterceptor() { + this.auditLogger = AuditLoggerFactory.getLogger(); + } + + @Override + public String id() { + return "audit-logging"; + } + + @Override + public int getPriority() { + return 50; // Low priority, runs after most other interceptors + } + + @Override + public boolean supportsPhase(LifecyclePhase phase) { + return phase == LifecyclePhase.PRE_REQUEST + || phase == LifecyclePhase.POST_REQUEST; + } + + @Override + public void intercept(RequestContext context, InterceptorChain chain) throws Exception { + if (context.getCurrentPhase() == LifecyclePhase.PRE_REQUEST) { + // Log request start + auditLogger.logRequestStart( + context.getRequestType(), + context.getOriginalSql(), + context.getSessionInfo() + ); + + chain.proceed(context); + + } else if (context.getCurrentPhase() == LifecyclePhase.POST_REQUEST) { + // Log request completion + long duration = context.getEndTimeMillis().orElse(System.currentTimeMillis()) + - context.getStartTimeMillis(); + + if (context.getException().isPresent()) { + auditLogger.logRequestFailure( + context.getRequestType(), + context.getOriginalSql(), + duration, + context.getException().get() + ); + } else { + auditLogger.logRequestSuccess( + context.getRequestType(), + context.getOriginalSql(), + duration + ); + } + + chain.proceed(context); + } + } +} +``` + +## Integration Points in StatementServiceImpl + +### Before (Current Code) + +```java +public void executeUpdate(StatementRequest request, StreamObserver responseObserver) { + log.info("Executing update {}", request.getSql()); + updateSessionActivity(request.getSession()); + String stmtHash = SqlStatementXXHash.hashSqlQuery(request.getSql()); + processClusterHealth(request.getSession()); + + try { + circuitBreaker.preCheck(stmtHash); // Hardcoded + + String connHash = request.getSession().getConnHash(); + SlowQuerySegregationManager manager = + getSlowQuerySegregationManagerForConnection(connHash); // Hardcoded + + OpResult result = manager.executeWithSegregation( // Hardcoded wrapper + stmtHash, + () -> executeUpdateInternal(request) + ); + + responseObserver.onNext(result); + responseObserver.onCompleted(); + circuitBreaker.onSuccess(stmtHash); // Hardcoded + + } catch (Exception e) { + circuitBreaker.onFailure(stmtHash, e); // Hardcoded + // ... error handling + } +} +``` + +### After (With Interceptors) + +```java +public void executeUpdate(StatementRequest request, StreamObserver responseObserver) { + // Create request context + RequestContext context = RequestContext.builder() + .requestType(RequestType.UPDATE) + .originalSql(request.getSql()) + .currentSql(request.getSql()) + .sessionInfo(request.getSession()) + .startTimeMillis(System.currentTimeMillis()) + .build(); + + try { + // Execute through interceptor chain + InterceptorChainExecutor.execute(context, ctx -> { + // Core execution logic (simplified) + OpResult result = executeUpdateInternal(request, ctx); + ctx.setResult(result); + }); + + // Send response + responseObserver.onNext((OpResult) context.getResult().orElseThrow()); + responseObserver.onCompleted(); + + } catch (Exception e) { + context.setException(e); + // Interceptors handle error recording + sendSQLExceptionMetadata(e, responseObserver); + } +} +``` + +Much cleaner! All the cross-cutting concerns are now in interceptors. + +## InterceptorChainExecutor Implementation + +```java +package org.openjproxy.interceptor; + +import java.util.List; + +/** + * Executes the interceptor chain for a request. + */ +public class InterceptorChainExecutor { + + /** + * Executes a request through the full interceptor chain. + * + * @param context the request context + * @param coreLogic the core business logic to execute + */ + public static void execute(RequestContext context, CoreLogic coreLogic) throws Exception { + // Get interceptors for this request type + List interceptors = RequestInterceptorRegistry.getInterceptors( + context.getRequestType(), + LifecyclePhase.PRE_REQUEST + ); + + // Execute PRE_REQUEST phase + context.setCurrentPhase(LifecyclePhase.PRE_REQUEST); + executePhase(context, interceptors); + + if (!context.isShortCircuited()) { + try { + // Execute PRE_EXECUTION phase + context.setCurrentPhase(LifecyclePhase.PRE_EXECUTION); + interceptors = RequestInterceptorRegistry.getInterceptors( + context.getRequestType(), + LifecyclePhase.PRE_EXECUTION + ); + executePhase(context, interceptors); + + // Execute RESOURCE_ACQUISITION phase + context.setCurrentPhase(LifecyclePhase.RESOURCE_ACQUISITION); + interceptors = RequestInterceptorRegistry.getInterceptors( + context.getRequestType(), + LifecyclePhase.RESOURCE_ACQUISITION + ); + executePhase(context, interceptors); + + // Execute core logic + context.setCurrentPhase(LifecyclePhase.EXECUTION); + coreLogic.execute(context); + + // Execute POST_EXECUTION phase + context.setCurrentPhase(LifecyclePhase.POST_EXECUTION); + interceptors = RequestInterceptorRegistry.getInterceptors( + context.getRequestType(), + LifecyclePhase.POST_EXECUTION + ); + executePhase(context, interceptors); + + // Execute RESOURCE_RELEASE phase + context.setCurrentPhase(LifecyclePhase.RESOURCE_RELEASE); + interceptors = RequestInterceptorRegistry.getInterceptors( + context.getRequestType(), + LifecyclePhase.RESOURCE_RELEASE + ); + executePhase(context, interceptors); + + } catch (Exception e) { + // Execute EXCEPTION_HANDLING phase + context.setException(e); + context.setCurrentPhase(LifecyclePhase.EXCEPTION_HANDLING); + interceptors = RequestInterceptorRegistry.getInterceptors( + context.getRequestType(), + LifecyclePhase.EXCEPTION_HANDLING + ); + executePhase(context, interceptors); + + // Re-throw if not handled + if (context.getException().isPresent()) { + throw context.getException().get(); + } + + } finally { + // Execute POST_REQUEST phase (always runs) + context.setCurrentPhase(LifecyclePhase.POST_REQUEST); + context.setEndTimeMillis(System.currentTimeMillis()); + interceptors = RequestInterceptorRegistry.getInterceptors( + context.getRequestType(), + LifecyclePhase.POST_REQUEST + ); + executePhase(context, interceptors); + } + } + } + + private static void executePhase(RequestContext context, List interceptors) + throws Exception { + if (context.isShortCircuited()) { + return; + } + + InterceptorChain chain = new DefaultInterceptorChain(interceptors, context); + chain.proceed(context); + } + + @FunctionalInterface + public interface CoreLogic { + void execute(RequestContext context) throws Exception; + } +} +``` + +## Benefits of This Approach + +### For OJP Core + +1. **Reduced Complexity**: `StatementServiceImpl` becomes much simpler +2. **Better Testability**: Each interceptor can be tested independently +3. **Easier Maintenance**: Changes to features don't require touching core code +4. **Clear Separation**: Cross-cutting concerns are separated from business logic + +### For OJP Users + +1. **Extensibility**: Users can add custom interceptors without modifying OJP +2. **Flexibility**: Interceptors can be enabled/disabled via configuration +3. **Composability**: Multiple interceptors work together seamlessly +4. **Standard Pattern**: Familiar Servlet Filter pattern + +### For Third-Party Providers + +1. **Easy Integration**: Just implement an interface and add to classpath +2. **No Recompilation**: Drop JAR in `ojp-libs` directory +3. **Full Control**: Access to entire request lifecycle +4. **Rich Context**: All request information available + +## Migration Path + +### Backward Compatibility + +During migration, both old and new approaches will coexist: + +1. **Phase 1**: Add interceptor infrastructure (no behavior change) +2. **Phase 2**: Implement interceptor versions of existing features +3. **Phase 3**: Run both old and new code in parallel (feature flags) +4. **Phase 4**: Gradually switch from old to new (per feature) +5. **Phase 5**: Remove old implementations + +### Feature Flags + +```properties +# ojp-server.properties +interceptor.enabled=true +interceptor.circuit-breaker.enabled=true +interceptor.slow-query.enabled=true +interceptor.sql-enhancer.enabled=true + +# During migration, can disable specific interceptors +interceptor.circuit-breaker.use-legacy=false +``` + +## Configuration + +### Interceptor Configuration File + +```properties +# interceptors.properties + +# Global settings +interceptor.enabled=true +interceptor.registry.scan-ojp-libs=true + +# Circuit Breaker +interceptor.circuit-breaker.enabled=true +interceptor.circuit-breaker.priority=300 +interceptor.circuit-breaker.open-duration-ms=5000 +interceptor.circuit-breaker.failure-threshold=3 + +# Slow Query Segregation +interceptor.slow-query.enabled=true +interceptor.slow-query.priority=200 +interceptor.slow-query.slow-slot-percentage=20 +interceptor.slow-query.idle-timeout-ms=5000 + +# SQL Enhancer +interceptor.sql-enhancer.enabled=true +interceptor.sql-enhancer.priority=500 +interceptor.sql-enhancer.dialect=postgresql +interceptor.sql-enhancer.optimization-enabled=true +``` + +## Performance Considerations + +### Overhead Analysis + +1. **Negligible When Empty**: If no interceptors registered, overhead is minimal (just a registry check) +2. **Linear Scaling**: Each interceptor adds constant time overhead +3. **Optimized Filtering**: Interceptors filtered by type/phase before execution +4. **No Reflection**: Direct method calls through interface + +### Benchmarks (Estimated) + +- **No interceptors**: < 0.01ms overhead +- **3 interceptors (typical)**: 0.05-0.1ms overhead +- **10 interceptors**: 0.2-0.3ms overhead + +Compared to typical query execution (10-1000ms), this is negligible. + +## Testing Strategy + +### Unit Tests + +1. Test each interceptor independently +2. Test `RequestContext` mutability +3. Test `InterceptorChain` logic +4. Test short-circuit behavior + +### Integration Tests + +1. Test interceptor chain execution +2. Test phase transitions +3. Test exception propagation +4. Test attribute passing between interceptors + +### Performance Tests + +1. Benchmark overhead with 0, 5, 10 interceptors +2. Test under concurrent load +3. Measure memory overhead + +## Security Considerations + +1. **Interceptor Validation**: Only load from trusted sources +2. **Exception Handling**: Interceptors should not expose sensitive data +3. **Context Isolation**: Interceptors should not leak information +4. **Resource Limits**: Prevent interceptors from consuming excessive resources + +## Documentation Required + +1. **New Document**: `Understanding-OJP-Interceptors.md` + - Comprehensive guide for interceptor developers + - Examples for common use cases + - Best practices + +2. **Update**: `Understanding-OJP-SPIs.md` + - Add section on RequestInterceptor SPI + - Link to interceptor guide + +3. **New ADR**: `adr-008-request-lifecycle-interceptor-pattern.md` + - Decision rationale + - Alternatives considered + - Trade-offs + +4. **API Javadoc**: Complete documentation for all interfaces + +## Comparison with Alternatives + +### Alternative 1: Event-Driven Architecture + +**Approach**: Publish events at lifecycle points, interceptors subscribe + +**Pros**: +- True decoupling +- Asynchronous processing possible + +**Cons**: +- More complex +- Harder to maintain order +- Difficult to handle exceptions +- Performance overhead from event dispatch + +**Verdict**: Chain of Responsibility is simpler and more appropriate + +### Alternative 2: Aspect-Oriented Programming (AOP) + +**Approach**: Use AOP framework (AspectJ, Spring AOP) for cross-cutting concerns + +**Pros**: +- Clean separation +- No code changes needed + +**Cons**: +- Requires AOP framework dependency +- Compile-time or load-time weaving complexity +- Harder to debug +- Less explicit + +**Verdict**: SPI + Chain pattern is more explicit and doesn't require framework + +### Alternative 3: Decorator Pattern + +**Approach**: Wrap StatementServiceImpl with decorators + +**Pros**: +- Classic OOP pattern +- Type-safe + +**Cons**: +- Compile-time composition only +- Not discoverable at runtime +- Can't add from external JARs easily + +**Verdict**: ServiceLoader + Chain is more flexible + +## Success Criteria + +This implementation will be considered successful when: + +1. ✅ All existing features (CircuitBreaker, SlowQuery, SqlEnhancer) migrated to interceptors +2. ✅ Zero performance regression (< 1% overhead) +3. ✅ Third-party interceptors can be loaded from ojp-libs +4. ✅ StatementServiceImpl reduced to < 1000 lines +5. ✅ Complete documentation and examples available +6. ✅ At least one community-contributed interceptor example + +## Timeline + +- **Week 1-2**: Core infrastructure and interfaces +- **Week 3-4**: Integration layer in StatementServiceImpl +- **Week 5-6**: Migrate CircuitBreaker +- **Week 7**: Migrate SlowQuerySegregation +- **Week 8**: Migrate SqlEnhancer +- **Week 9-10**: Documentation, testing, examples +- **Week 11**: Beta testing with community +- **Week 12**: Final release + +## Conclusion + +The Request Lifecycle Interceptor Pattern provides a powerful, standardized way for libraries and modules to integrate with OJP's request processing flow. By adopting this pattern, OJP will: + +1. Become more extensible and maintainable +2. Enable third-party integrations without code changes +3. Follow established patterns (Servlet Filters) +4. Maintain backward compatibility during migration +5. Provide a clear, documented way for providers to extend OJP + +This pattern aligns perfectly with OJP's existing use of ServiceLoader for SPIs and extends the same philosophy to request lifecycle management. + +## Next Steps + +1. **Review**: Get feedback from OJP team and community +2. **Prototype**: Build proof-of-concept with one interceptor +3. **Refine**: Adjust based on prototype learnings +4. **ADR**: Create formal architectural decision record +5. **Implement**: Follow the phased implementation plan + +--- + +**Document Version**: 1.0 +**Author**: OJP Architecture Team +**Date**: 2026-02-01 +**Status**: DRAFT - PENDING REVIEW diff --git a/documents/designs/REQUEST_LIFECYCLE_INTERCEPTOR_VISUAL.md b/documents/designs/REQUEST_LIFECYCLE_INTERCEPTOR_VISUAL.md new file mode 100644 index 000000000..2e96742a1 --- /dev/null +++ b/documents/designs/REQUEST_LIFECYCLE_INTERCEPTOR_VISUAL.md @@ -0,0 +1,562 @@ +# Request Lifecycle Interceptor Pattern - Visual Reference + +## Overview Diagram + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ REQUEST LIFECYCLE FLOW │ +│ (With Interceptor Integration Points) │ +└─────────────────────────────────────────────────────────────────────────┘ + +Client Request + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ PHASE 1: PRE_REQUEST │ +│ • Session validation & activity tracking │ +│ • Cluster health processing │ +│ • Request enrichment & metadata addition │ +│ │ +│ Interceptor Examples: │ +│ • AuthenticationInterceptor (priority: 1000) │ +│ • RateLimitingInterceptor (priority: 900) │ +│ • RequestLoggingInterceptor (priority: 100) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ PHASE 2: PRE_EXECUTION │ +│ • SQL hash generation │ +│ • Circuit breaker pre-check │ +│ • SQL transformation & optimization │ +│ │ +│ Interceptor Examples: │ +│ • SqlEnhancerInterceptor (priority: 500) ← Apache Calcite │ +│ • CircuitBreakerInterceptor (priority: 300) │ +│ • QueryValidationInterceptor (priority: 250) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ PHASE 3: RESOURCE_ACQUISITION │ +│ • Slow query slot acquisition │ +│ • Connection pool acquisition │ +│ • Transaction context setup │ +│ │ +│ Interceptor Examples: │ +│ • SlowQueryInterceptor (priority: 200) ← Slot management │ +│ • ConnectionLeaseInterceptor (priority: 150) │ +│ • ResourceTrackingInterceptor (priority: 100) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ PHASE 4: EXECUTION │ +│ • Statement preparation │ +│ • Parameter binding │ +│ • ACTUAL DATABASE EXECUTION ★ │ +│ │ +│ Interceptor Examples: │ +│ • QueryTimeoutInterceptor (priority: 300) │ +│ • ExecutionMonitorInterceptor (priority: 200) │ +│ • TracingInterceptor (priority: 100) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ PHASE 5: POST_EXECUTION │ +│ • Result set processing │ +│ • Metadata extraction │ +│ • Performance data recording │ +│ │ +│ Interceptor Examples: │ +│ • ResultCacheInterceptor (priority: 300) │ +│ • PerformanceMonitorInterceptor (priority: 200) │ +│ • ResultTransformInterceptor (priority: 150) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ PHASE 6: RESOURCE_RELEASE │ +│ • Connection release back to pool │ +│ • Slow query slot release │ +│ • Resource cleanup │ +│ │ +│ Interceptor Examples: │ +│ • SlowQueryInterceptor (priority: 200) ← Slot release │ +│ • ConnectionReleaseInterceptor (priority: 150) │ +│ • LeakDetectionInterceptor (priority: 100) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ PHASE 7: POST_REQUEST │ +│ • Success/failure state recording │ +│ • Metrics publishing │ +│ • Audit logging │ +│ │ +│ Interceptor Examples: │ +│ • CircuitBreakerInterceptor (priority: 300) ← Success/failure │ +│ • MetricsPublisherInterceptor (priority: 200) │ +│ • AuditLoggerInterceptor (priority: 100) │ +│ • AlertingInterceptor (priority: 50) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Response to Client + +═══════════════════════════════════════════════════════════════════════════ + EXCEPTION FLOW +═══════════════════════════════════════════════════════════════════════════ + +Any Phase Exception + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ PHASE X: EXCEPTION_HANDLING │ +│ • Exception transformation │ +│ • Failure state recording │ +│ • Retry logic (if applicable) │ +│ • Error recovery attempts │ +│ │ +│ Interceptor Examples: │ +│ • CircuitBreakerInterceptor (priority: 300) ← Failure recording │ +│ • RetryInterceptor (priority: 250) │ +│ • ExceptionTransformInterceptor (priority: 200) │ +│ • ErrorNotificationInterceptor (priority: 100) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ├─── Exception Handled → Continue to POST_REQUEST + │ + └─── Exception Propagated → Error Response to Client +``` + +## Interceptor Chain Execution + +``` +┌──────────────────────────────────────────────────────────────────────────┐ +│ CHAIN OF RESPONSIBILITY │ +└──────────────────────────────────────────────────────────────────────────┘ + +Request arrives + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ RequestInterceptorRegistry │ +│ • Discovers interceptors via ServiceLoader │ +│ • Filters by RequestType and LifecyclePhase │ +│ • Sorts by priority (highest first) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ InterceptorChain.proceed(context) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ Interceptor #1 (Highest Priority) │ +│ intercept(context, chain) { │ +│ // Pre-processing │ +│ doPreWork(); │ +│ │ +│ // Proceed to next interceptor │ +│ chain.proceed(context); ───────────┐ │ +│ │ │ +│ // Post-processing │ │ +│ doPostWork(); │ │ +│ } │ │ +└─────────────────────────────────────────┘ │ + │ │ + ▼ │ +┌─────────────────────────────────────────────────────────────────────────┐ +│ Interceptor #2 │ +│ intercept(context, chain) { │ +│ doPreWork(); │ +│ chain.proceed(context); ───────────┐ │ +│ doPostWork(); │ │ +│ } │ │ +└───────────────────────────────────────┘ │ + │ │ + ▼ │ +┌─────────────────────────────────────────────────────────────────────────┐ +│ Interceptor #3 │ +│ intercept(context, chain) { │ +│ doPreWork(); │ +│ chain.proceed(context); ───────────┐ │ +│ doPostWork(); │ │ +│ } │ │ +└───────────────────────────────────────┘ │ + │ │ + ▼ │ +┌─────────────────────────────────────────────────────────────────────────┐ +│ Core Business Logic │ +│ • executeUpdateInternal() or │ +│ • executeQueryInternal() │ +│ • Actual database operation │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + │ Returns + ▼ + Interceptor #3 Post-processing + │ + ▼ + Interceptor #2 Post-processing + │ + ▼ + Interceptor #1 Post-processing + │ + ▼ + Response +``` + +## Current vs. Proposed Architecture + +### Current Architecture (Hard-coded Integration) + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ StatementServiceImpl │ +│ (2,528 lines) │ +│ │ +│ executeUpdate(request) { │ +│ updateSessionActivity(); ← Always hardcoded │ +│ hash = hashSql(); ← Always hardcoded │ +│ circuitBreaker.preCheck(hash); ← Always hardcoded ❌ │ +│ manager = getSlowQuery(conn); ← Always hardcoded ❌ │ +│ result = manager.executeWith(...); ← Always hardcoded ❌ │ +│ circuitBreaker.onSuccess(hash); ← Always hardcoded ❌ │ +│ } │ +│ │ +│ executeQuery(request) { │ +│ // Similar hard-coded logic │ +│ if (enhancerEnabled) { ← Configuration flag ❌ │ +│ sql = enhancer.enhance(sql); ← Conditional call ❌ │ +│ } │ +│ // More hard-coded calls... │ +│ } │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ + +Problems: + ❌ Tight coupling + ❌ Can't add features without modifying core + ❌ No third-party extensibility + ❌ Each feature integrates differently + ❌ Hard to test in isolation +``` + +### Proposed Architecture (Interceptor Pattern) + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ StatementServiceImpl │ +│ (<1,000 lines) │ +│ │ +│ executeUpdate(request) { │ +│ context = RequestContext.builder() │ +│ .requestType(UPDATE) │ +│ .sql(request.getSql()) │ +│ .build(); │ +│ │ +│ InterceptorChainExecutor.execute(context, ctx -> { │ +│ result = executeUpdateInternal(request, ctx); │ +│ ctx.setResult(result); │ +│ }); │ +│ } │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + │ delegates to + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ InterceptorChainExecutor │ +│ • Discovers interceptors from registry │ +│ • Executes each lifecycle phase │ +│ • Manages exception handling │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + │ loads from + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ RequestInterceptorRegistry (ServiceLoader) │ +│ │ +│ Built-in interceptors: │ +│ • CircuitBreakerInterceptor ✅ │ +│ • SlowQueryInterceptor ✅ │ +│ • SqlEnhancerInterceptor ✅ │ +│ │ +│ Third-party interceptors: │ +│ • AcmeQueryLoggerInterceptor ✅ (from ojp-libs/) │ +│ • CustomRateLimitInterceptor ✅ (from ojp-libs/) │ +│ • ... any provider can add more ... ✅ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ + +Benefits: + ✅ Loose coupling + ✅ Add features without modifying core + ✅ Third-party extensibility via ServiceLoader + ✅ Standardized integration pattern + ✅ Easy to test interceptors individually + ✅ Clean separation of concerns +``` + +## Interface Hierarchy + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Core Interfaces │ +└─────────────────────────────────────────────────────────────────────────┘ + +RequestInterceptor (SPI) + ├── id(): String + ├── getPriority(): int + ├── isAvailable(): boolean + ├── supportsRequestType(RequestType): boolean + ├── supportsPhase(LifecyclePhase): boolean + └── intercept(RequestContext, InterceptorChain): void + │ + ├─── Uses ──┐ + │ │ + ▼ ▼ + RequestContext InterceptorChain + │ │ + │ └── proceed(RequestContext): void + │ + ├── getRequestType(): RequestType + ├── getCurrentPhase(): LifecyclePhase + ├── getOriginalSql(): String + ├── getCurrentSql(): String + ├── setCurrentSql(String): void + ├── getSqlHash(): String + ├── getConnection(): Optional + ├── getResult(): Optional + ├── getException(): Optional + ├── getAttribute(String): Object + └── setAttribute(String, Object): void + +┌─────────────────────────────────────────────────────────────────────────┐ +│ Supporting Types │ +└─────────────────────────────────────────────────────────────────────────┘ + +RequestType (Enum) + ├── QUERY + ├── UPDATE + ├── BATCH + ├── CALLABLE + ├── TRANSACTION + ├── XA_OPERATION + ├── CONNECTION + ├── RESULT_SET_FETCH + └── LOB_OPERATION + +LifecyclePhase (Enum) + ├── PRE_REQUEST + ├── PRE_EXECUTION + ├── RESOURCE_ACQUISITION + ├── EXECUTION + ├── POST_EXECUTION + ├── RESOURCE_RELEASE + ├── POST_REQUEST + └── EXCEPTION_HANDLING +``` + +## Priority Ranges (Recommended) + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Priority Range │ Purpose │ Examples │ +├────────────────┼────────────────────────────┼────────────────────────────┤ +│ 1000+ │ Critical Infrastructure │ • Authentication │ +│ │ Must run first │ • Rate Limiting │ +│ │ │ • Security Checks │ +├────────────────┼────────────────────────────┼────────────────────────────┤ +│ 500-999 │ Request Transformation │ • SQL Enhancement (500) │ +│ │ Modify before execution │ • Query Rewriting │ +│ │ │ • SQL Validation │ +├────────────────┼────────────────────────────┼────────────────────────────┤ +│ 100-499 │ Resource Management │ • Circuit Breaker (300) │ +│ │ Control execution flow │ • Slow Query Seg. (200) │ +│ │ │ • Connection Mgmt (150) │ +├────────────────┼────────────────────────────┼────────────────────────────┤ +│ 0-99 │ Monitoring & Logging │ • Metrics (100) │ +│ │ Observe without changing │ • Tracing (75) │ +│ │ │ • Logging (50) │ +├────────────────┼────────────────────────────┼────────────────────────────┤ +│ Negative │ Post-processing │ • Cleanup (-10) │ +│ │ Final cleanup tasks │ • Deferred logging (-20) │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## Example: Third-Party Integration + +### Step 1: Implement RequestInterceptor + +```java +package com.acme.ojp; + +import org.openjproxy.interceptor.*; + +public class QueryLoggerInterceptor implements RequestInterceptor { + + @Override + public String id() { + return "acme-query-logger"; + } + + @Override + public int getPriority() { + return 50; // Monitoring priority + } + + @Override + public boolean supportsPhase(LifecyclePhase phase) { + return phase == LifecyclePhase.POST_REQUEST; + } + + @Override + public void intercept(RequestContext context, InterceptorChain chain) + throws Exception { + long duration = context.getEndTimeMillis().orElse(0L) + - context.getStartTimeMillis(); + + logger.info("Query: sql={}, duration={}ms", + context.getOriginalSql(), duration); + + chain.proceed(context); + } +} +``` + +### Step 2: Register via ServiceLoader + +Create file: `META-INF/services/org.openjproxy.interceptor.RequestInterceptor` + +``` +com.acme.ojp.QueryLoggerInterceptor +``` + +### Step 3: Package and Deploy + +```bash +# Package into JAR +mvn clean package + +# Deploy to OJP +cp target/acme-ojp-interceptor.jar /path/to/ojp-libs/ + +# Restart OJP Server +# Interceptor automatically discovered and loaded! +``` + +### Result + +``` +[INFO] Registered RequestInterceptor: acme-query-logger (priority: 50) +[INFO] Query: sql=SELECT * FROM users, duration=45ms +``` + +No OJP recompilation needed! ✅ + +## Performance Characteristics + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Overhead Analysis │ +└─────────────────────────────────────────────────────────────────────────┘ + +Scenario Overhead Impact +───────────────────────────────────────────────────────────────────────── +No interceptors < 0.01ms Negligible +3 interceptors (typical) 0.05-0.1ms Negligible +10 interceptors 0.2-0.3ms Negligible + +Compared to: + Fast query (10ms) ~1% ✅ Acceptable + Average query (100ms) ~0.1% ✅ Negligible + Slow query (1000ms) ~0.01% ✅ Negligible + +Optimization: + • Interceptors filtered by type/phase before execution + • No reflection overhead (direct interface calls) + • Registry caching (loaded once at startup) + • Short-circuit support (stop chain early if needed) +``` + +## Module Structure + +``` +ojp/ +├── ojp-interceptor-api/ ← NEW MODULE +│ └── src/main/java/org/openjproxy/interceptor/ +│ ├── RequestInterceptor.java ← SPI Interface +│ ├── RequestContext.java ← Context object +│ ├── InterceptorChain.java ← Chain interface +│ ├── RequestType.java ← Enum +│ ├── LifecyclePhase.java ← Enum +│ └── RequestInterceptorRegistry.java ← ServiceLoader registry +│ +├── ojp-server/ +│ └── src/main/java/org/openjproxy/grpc/server/ +│ ├── interceptor/ ← Built-in interceptors +│ │ ├── CircuitBreakerInterceptor.java +│ │ ├── SlowQueryInterceptor.java +│ │ └── SqlEnhancerInterceptor.java +│ │ +│ └── StatementServiceImpl.java ← Uses interceptors +│ +└── ojp-libs/ ← External JARs + ├── acme-ojp-interceptor.jar ← Third-party interceptors + └── custom-interceptor.jar ← Custom implementations +``` + +## Migration Timeline + +``` +Week 1-2 ┃ Core Infrastructure + ┃ • Create ojp-interceptor-api module + ┃ • Implement interfaces and registry + ┃ • Add ServiceLoader support + ┃ +Week 3-4 ┃ Integration Layer + ┃ • Add interceptor invocation to StatementServiceImpl + ┃ • Implement InterceptorChainExecutor + ┃ • Add phase transitions + ┃ +Week 5-6 ┃ Migrate Circuit Breaker + ┃ • Implement CircuitBreakerInterceptor + ┃ • Run parallel with legacy code + ┃ • Switch via feature flag + ┃ +Week 7 ┃ Migrate Slow Query Segregation + ┃ • Implement SlowQueryInterceptor + ┃ • Test slot management + ┃ • Switch via feature flag + ┃ +Week 8 ┃ Migrate SQL Enhancer + ┃ • Implement SqlEnhancerInterceptor + ┃ • Test SQL transformation + ┃ • Switch via feature flag + ┃ +Week 9-10 ┃ Documentation & Examples + ┃ • Write comprehensive docs + ┃ • Create example interceptors + ┃ • Prepare migration guide + ┃ +Week 11 ┃ Beta Testing + ┃ • Community testing + ┃ • Gather feedback + ┃ • Fix issues + ┃ +Week 12 ┃ Final Release + ┃ • Remove legacy code + ┃ • Publish documentation + ┃ • Announce release +``` + +--- + +**Document Version**: 1.0 +**Last Updated**: 2026-02-01 +**Status**: DRAFT - PENDING REVIEW