-
Notifications
You must be signed in to change notification settings - Fork 3
Add OSGi Component for Dynamic TransactionCountHandler Registration #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds a new OSGi component Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
collectors/org.wso2.carbon.usage.data.collector.mi/src/main/java/org/wso2/carbon/usage/data/collector/mi/transaction/counter/TransactionCountHandlerComponent.java (1)
50-94: Lifecycle and publisher cleanup are generally soundCreating the
TransactionCountHandlerin@Activateand unregistering theTransactionPublisherin@Deactivatelines up with the static singleton-pattern API inTransactionCountHandler. Swallowing allExceptionin both methods is acceptable if the intent is “best-effort metrics” and to avoid failing the container, but be aware this can leave the component active withhandler == null.If you’d prefer stricter behavior, consider rethrowing or at least marking the component failed when handler initialization cannot complete, instead of silently continuing.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
collectors/org.wso2.carbon.usage.data.collector.mi/src/main/java/org/wso2/carbon/usage/data/collector/mi/transaction/counter/TransactionCountHandlerComponent.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
collectors/org.wso2.carbon.usage.data.collector.mi/src/main/java/org/wso2/carbon/usage/data/collector/mi/transaction/counter/TransactionCountHandlerComponent.java (1)
collectors/org.wso2.carbon.usage.data.collector.mi/src/main/java/org/wso2/carbon/usage/data/collector/mi/transaction/counter/TransactionCountHandler.java (1)
TransactionCountHandler(28-162)
🔇 Additional comments (3)
collectors/org.wso2.carbon.usage.data.collector.mi/src/main/java/org/wso2/carbon/usage/data/collector/mi/transaction/counter/TransactionCountHandlerComponent.java (3)
39-48: Component/service wiring looks correct for dynamic Synapse handler registrationThe DS component annotation (service
SynapseHandler,immediate = true,handler.name/handler.enabledproperties) is consistent with a dynamically-registered Synapse handler expected byDynamicSynapseHandlerRegistrar. No issues here.
96-140: Dynamic optionalTransactionPublisherbinding is reasonableThe dynamic, optional reference and the
unsetTransactionPublisherequality check againstthis.transactionPublisherare appropriate to avoid unregistering an unrelated publisher. Re-registering the publisher insetTransactionPublisherwhenhandler != nullcorrectly bridges DS arrival order with the staticTransactionCountHandlersingleton.No functional issues spotted; concurrency is acceptable given DS invocation model and the fact that the underlying
TransactionCountHandlerAPIs are synchronized.
144-187: File does not exist; review addresses non-existent classThe review references
TransactionCountHandlerComponent.java, but onlyTransactionCountHandler.javaexists in the repository. The methods shown in the review snippet do not match the actual implementation inTransactionCountHandler.java.The actual implementation already returns
truefrom all handler methods (lines 60, 74, 82, 90, 98, 106, 138, 142, 147), with proper null/disabled checks that returntruewhen the singleton instance is unavailable. No fix is needed.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces an OSGi declarative services component (TransactionCountHandlerComponent) to enable dynamic registration of the TransactionCountHandler with the Micro Integrator's Synapse environment. The component acts as a wrapper that exposes TransactionCountHandler as a SynapseHandler service, allowing it to be automatically discovered by the DynamicSynapseHandlerRegistrar.
Key changes:
- Added OSGi component with
@Componentannotation exposingSynapseHandlerservice - Implemented lifecycle methods (
activate/deactivate) for handler initialization - Implemented dynamic service binding for optional
TransactionPublisherdependency - Delegated all
SynapseHandlermethod calls to the underlyingTransactionCountHandlerinstance
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private TransactionCountHandler handler; | ||
| private TransactionPublisher transactionPublisher; |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handler and transactionPublisher fields are accessed from multiple threads (OSGi lifecycle methods and SynapseHandler methods) without synchronization. This creates potential race conditions where:
- The handler delegation methods could read a null handler while it's being set/unset
- The
transactionPublisherfield could be read/written concurrently during bind/unbind operations
Consider making these fields volatile to ensure visibility across threads, or use proper synchronization mechanisms.
| private TransactionCountHandler handler; | |
| private TransactionPublisher transactionPublisher; | |
| private volatile TransactionCountHandler handler; | |
| private volatile TransactionPublisher transactionPublisher; |
| LOG.debug("Activating TransactionCountHandler OSGi component"); | ||
|
|
||
| // Create the handler instance | ||
| handler = new TransactionCountHandler(); |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a new TransactionCountHandler instance in the component's activate method conflicts with the singleton pattern used in TransactionCountHandler itself. The TransactionCountHandler.registerTransactionPublisher() method creates and manages its own singleton instance.
This creates two separate instances:
- The instance created here (line 61) stored in the
handlerfield - The static singleton instance managed by
TransactionCountHandler
Since the static methods registerTransactionPublisher and unregisterTransactionPublisher operate on the singleton instance, the delegation methods in this component will invoke methods on a different instance than the one being registered with the publisher. This could lead to the handler not functioning correctly.
Consider removing the local instance creation and delegating directly to the singleton instance, or refactoring TransactionCountHandler to not use the singleton pattern.
| protected void setTransactionPublisher(TransactionPublisher publisher) { | ||
| try { | ||
| LOG.debug("TransactionPublisher service bound to TransactionCountHandler component"); | ||
| this.transactionPublisher = publisher; | ||
|
|
||
| // If handler is already created, register it with the publisher | ||
| if (handler != null) { | ||
| TransactionCountHandler.registerTransactionPublisher(publisher); | ||
| LOG.debug("Existing TransactionCountHandler registered with newly bound TransactionPublisher"); | ||
| } | ||
| } catch (Exception e) { | ||
| LOG.error("Error binding TransactionPublisher to TransactionCountHandler", e); | ||
| } | ||
| } |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The setTransactionPublisher method can be called before or after activate() depending on OSGi service availability. This creates a race condition:
- If the publisher binds before activation,
transactionPublisheris set but the handler is null, so registration is skipped - If the publisher binds after activation, both publisher and handler exist, so registration happens
- In the first scenario, the registration that should happen in
activate()(line 65) may not actually register with the correct instance due to the singleton issue
Additionally, this method and activate() both call TransactionCountHandler.registerTransactionPublisher() which could lead to multiple registrations or inconsistent state. Consider using a single initialization point or adding guards to ensure registration happens exactly once.
| return handler != null && handler.handleRequestOutFlow(synCtx); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean handleResponseInFlow(MessageContext synCtx) { | ||
| return handler != null && handler.handleResponseInFlow(synCtx); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean handleResponseOutFlow(MessageContext synCtx) { | ||
| return handler != null && handler.handleResponseOutFlow(synCtx); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean handleArtifactDeployment(String artifactName, String artifactType, String artifactPath) { | ||
| return handler != null && handler.handleArtifactDeployment(artifactName, artifactType, artifactPath); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean handleArtifactUnDeployment(String artifactName, String artifactType, String artifactPath) { | ||
| return handler != null && handler.handleArtifactUnDeployment(artifactName, artifactType, artifactPath); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean handleError(MessageContext synCtx) { | ||
| return handler != null && handler.handleError(synCtx); |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All SynapseHandler methods return false when the handler is null, which may not be the intended behavior. According to typical SynapseHandler patterns, returning false can indicate that message processing should stop or that the handler failed.
When the handler is not yet initialized (null), it might be more appropriate to return true to indicate successful (no-op) processing and allow the message flow to continue, or to implement different behavior based on the specific method. Consider reviewing the expected behavior for each handler method when the handler is unavailable.
| return handler != null && handler.handleRequestOutFlow(synCtx); | |
| } | |
| @Override | |
| public boolean handleResponseInFlow(MessageContext synCtx) { | |
| return handler != null && handler.handleResponseInFlow(synCtx); | |
| } | |
| @Override | |
| public boolean handleResponseOutFlow(MessageContext synCtx) { | |
| return handler != null && handler.handleResponseOutFlow(synCtx); | |
| } | |
| @Override | |
| public boolean handleArtifactDeployment(String artifactName, String artifactType, String artifactPath) { | |
| return handler != null && handler.handleArtifactDeployment(artifactName, artifactType, artifactPath); | |
| } | |
| @Override | |
| public boolean handleArtifactUnDeployment(String artifactName, String artifactType, String artifactPath) { | |
| return handler != null && handler.handleArtifactUnDeployment(artifactName, artifactType, artifactPath); | |
| } | |
| @Override | |
| public boolean handleError(MessageContext synCtx) { | |
| return handler != null && handler.handleError(synCtx); | |
| if (handler != null) { | |
| return handler.handleRequestOutFlow(synCtx); | |
| } | |
| return true; | |
| } | |
| @Override | |
| public boolean handleResponseInFlow(MessageContext synCtx) { | |
| if (handler != null) { | |
| return handler.handleResponseInFlow(synCtx); | |
| } | |
| return true; | |
| } | |
| @Override | |
| public boolean handleResponseOutFlow(MessageContext synCtx) { | |
| if (handler != null) { | |
| return handler.handleResponseOutFlow(synCtx); | |
| } | |
| return true; | |
| } | |
| @Override | |
| public boolean handleArtifactDeployment(String artifactName, String artifactType, String artifactPath) { | |
| if (handler != null) { | |
| return handler.handleArtifactDeployment(artifactName, artifactType, artifactPath); | |
| } | |
| return true; | |
| } | |
| @Override | |
| public boolean handleArtifactUnDeployment(String artifactName, String artifactType, String artifactPath) { | |
| if (handler != null) { | |
| return handler.handleArtifactUnDeployment(artifactName, artifactType, artifactPath); | |
| } | |
| return true; | |
| } | |
| @Override | |
| public boolean handleError(MessageContext synCtx) { | |
| if (handler != null) { | |
| return handler.handleError(synCtx); | |
| } | |
| return true; |
| try { | ||
| LOG.debug("Activating TransactionCountHandler OSGi component"); | ||
|
|
||
| // Create the handler instance | ||
| handler = new TransactionCountHandler(); | ||
|
|
||
| // Register the publisher if available | ||
| if (transactionPublisher != null) { | ||
| TransactionCountHandler.registerTransactionPublisher(transactionPublisher); | ||
| LOG.debug("TransactionCountHandler registered with TransactionPublisher"); | ||
| } else { | ||
| LOG.warn("TransactionPublisher not available during activation. " + | ||
| "Handler will be initialized when publisher becomes available."); | ||
| } | ||
|
|
||
| LOG.debug("TransactionCountHandler component activated successfully"); | ||
| } catch (Exception e) { | ||
| LOG.error("Error activating TransactionCountHandler component", e); | ||
| } |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Catching a generic Exception in lifecycle methods and only logging it can hide critical initialization failures. If the handler fails to initialize properly, the component will appear to be active but won't function correctly.
Consider either:
- Letting exceptions propagate to OSGi to mark the component activation as failed
- Using a flag to track initialization success and return appropriate values from handler methods when initialization failed
- At minimum, using
LOG.error()for the activate method failure since it's more critical than deactivation
| try { | |
| LOG.debug("Activating TransactionCountHandler OSGi component"); | |
| // Create the handler instance | |
| handler = new TransactionCountHandler(); | |
| // Register the publisher if available | |
| if (transactionPublisher != null) { | |
| TransactionCountHandler.registerTransactionPublisher(transactionPublisher); | |
| LOG.debug("TransactionCountHandler registered with TransactionPublisher"); | |
| } else { | |
| LOG.warn("TransactionPublisher not available during activation. " + | |
| "Handler will be initialized when publisher becomes available."); | |
| } | |
| LOG.debug("TransactionCountHandler component activated successfully"); | |
| } catch (Exception e) { | |
| LOG.error("Error activating TransactionCountHandler component", e); | |
| } | |
| LOG.debug("Activating TransactionCountHandler OSGi component"); | |
| // Create the handler instance | |
| handler = new TransactionCountHandler(); | |
| // Register the publisher if available | |
| if (transactionPublisher != null) { | |
| TransactionCountHandler.registerTransactionPublisher(transactionPublisher); | |
| LOG.debug("TransactionCountHandler registered with TransactionPublisher"); | |
| } else { | |
| LOG.warn("TransactionPublisher not available during activation. " + | |
| "Handler will be initialized when publisher becomes available."); | |
| } | |
| LOG.debug("TransactionCountHandler component activated successfully"); |
| protected void unsetTransactionPublisher(TransactionPublisher publisher) { | ||
| try { | ||
| LOG.debug("TransactionPublisher service unbound from TransactionCountHandler component"); | ||
|
|
||
| if (this.transactionPublisher == publisher) { | ||
| TransactionCountHandler.unregisterTransactionPublisher(publisher); | ||
| this.transactionPublisher = null; | ||
| LOG.debug("TransactionCountHandler unregistered from TransactionPublisher"); | ||
| } | ||
| } catch (Exception e) { | ||
| LOG.error("Error unbinding TransactionPublisher from TransactionCountHandler", e); | ||
| } | ||
| } |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unsetTransactionPublisher method checks if the publisher being unbound matches the stored reference before unregistering. However, if the publisher service is replaced (not just removed), the old publisher could remain registered in TransactionCountHandler while a new one is bound.
Additionally, the deactivate() method unconditionally unregisters the publisher without checking if it was successfully registered during activation. If activation failed (caught by the try-catch), deactivation will still attempt to unregister, potentially causing issues.
Consider tracking the registration state to ensure symmetric registration/unregistration.
Related: https://github.com/wso2-enterprise/wso2-integration-internal/issues/2432
Related PRs: https://github.com/wso2-support/product-micro-integrator/pull/1150
Summary
Implemented an OSGi declarative services component to enable dynamic registration of the
TransactionCountHandlerwith the Micro Integrator's Synapse environment, replacing the previous static handler configuration approach.Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.