Feature ETP-3003: Enhance call sync#858
Feature ETP-3003: Enhance call sync#858sebastianbarrozo wants to merge 4 commits intoprerelease/26.1from
Conversation
| package com.etendoerp.db; | ||
|
|
||
| public class CallAsyncProcess { | ||
| } |
There was a problem hiding this comment.
Suggestion
Consider either implementing the intended asynchronous process invocation logic (following existing Etendo async/background process patterns) or removing this empty class until it is needed, to avoid leaving unused stubs in the codebase.
+public class CallAsyncProcess {
+
+ // TODO: Implement async process invocation logic or remove this class if unused.
+Rationale: Empty public classes without fields or methods can cause confusion about their purpose, complicate maintenance, and risk becoming dead code. Etendo async/background processing usually follows well-defined patterns (e.g., DAL-based background processes or the asyncprocess module), so helper classes should encapsulate concrete behavior aligned with those patterns.
| processCriteria.add(Restrictions.eq(Process.PROPERTY_PROCEDURE, processName)); | ||
|
|
||
| final Process process = (Process) processCriteria.uniqueResult(); | ||
|
|
There was a problem hiding this comment.
[RESOLVED] > [!WARNING]
uniqueResult() will throw a NonUniqueResultException if more than one process shares the same procedure name. If that can happen due to bad data, it is safer to explicitly validate the result list size and raise a controlled OBException, similar to the previous implementation.
+ final Process process = (Process) processCriteria.uniqueResult();
+
+ if (processCriteria.list().size() > 1) {
+ throw new OBException("More than one process found with procedure name " + processName);
+ }Rationale: The original code explicitly checked that there was exactly one matching process and threw an OBException otherwise. The new implementation will now surface a Hibernate NonUniqueResultException if there are duplicates, which is less controlled and may not be properly localized or handled. Restoring an explicit size check keeps behavior consistent and avoids unexpected runtime failures.
| // Standard calls usually don't return a result set (they update AD_PInstance table) | ||
| // However, on Postgres they are SELECTs, so returnResults=true prevents syntax errors | ||
| boolean isPostgre = "POSTGRE".equals(new DalConnectionProvider(false).getRDBMS()); | ||
|
|
There was a problem hiding this comment.
Suggestion
Cache the DalConnectionProvider in a local variable and use equalsIgnoreCase for the RDBMS check. This avoids creating multiple provider instances and is more robust to case differences in configuration values.
+ final DalConnectionProvider cp = new DalConnectionProvider(false);
+ final boolean isPostgre = "POSTGRE".equalsIgnoreCase(cp.getRDBMS());Rationale: Existing Etendo code (for example, SQLC-generated classes and CallStoredProcedure tests) typically caches the ConnectionProvider and performs case-insensitive RDBMS checks. Reusing that pattern avoids potential surprises if configuration values change case and prevents unnecessary object creation in a method that may be called frequently.
| sb.append(")"); | ||
|
|
||
| if (returnResults || !isOracle) { | ||
| sb.append(" AS RESULT FROM DUAL"); |
There was a problem hiding this comment.
Suggestion
Consider documenting the expected SQL shape for Oracle vs PostgreSQL (CALL vs SELECT ... FROM DUAL) and how it interacts with procedures/functions that return a value. This will help future maintainers understand why buildSqlQuery mixes CALL and SELECT syntaxes and when returnResults should be true.
+ if (isOracle && !returnResults) {Rationale: The mix of different SQL forms (CALL vs SELECT name(...) AS RESULT FROM DUAL) is subtle and driven by DB-specific behavior. Explicit Javadoc or inline comments reduce the risk of regressions when adding new stored procedures or supporting additional databases.
| @@ -62,230 +69,288 @@ public static synchronized void setInstance(CallProcess instance) { | |||
| CallProcess.instance = instance; | |||
| } | |||
|
|
|||
There was a problem hiding this comment.
Suggestion
Wrap the SQLException in an OBException instead of IllegalStateException so that errors propagate through the standard Etendo exception handling and translation mechanism.
+ } catch (SQLException e) {
+ // Use OBException to integrate with Etendo error handling and translation
+ throw new OBException("Error executing raw process " + procedureName, e);Rationale: Etendo guidelines recommend using OBException (or subclasses) for business/runtime exceptions that need to be surfaced to the application layer, rather than generic runtime exceptions. This also aligns better with existing DAL and process code which expects OBException for controlled failures.
| return call(processName, recordID, parameters, null); | ||
| private void setParameters(PreparedStatement ps, List<Object> parameters, List<Class<?>> types) throws SQLException { | ||
| if (parameters == null || parameters.isEmpty()) return; | ||
|
|
There was a problem hiding this comment.
Suggestion
Use a multi-line if block instead of a single-line early return to keep style consistent with the rest of the class and typical Etendo formatting conventions.
+ if (parameters == null || parameters.isEmpty()) {
+ return;
+ }Rationale: While functionally equivalent, multi-line blocks improve readability and reduce the chance of errors when extending the method (e.g., adding logging before the return). Most core Etendo code follows this style.
etendobot
left a comment
There was a problem hiding this comment.
Changes requested by agent. Please resolve blocking issues.
b666d32 to
494597b
Compare
| * | ||
| * @author mtaal | ||
| * Facade for executing Stored Procedures. | ||
| * Delegates all logic to {@link CallProcess#executeRaw}. |
There was a problem hiding this comment.
Suggestion
Clarify in the Javadoc that this class is now just a backward-compatible façade over CallProcess#executeRaw, so developers know where the actual implementation lives and which entry point to prefer for new code.
+ * Facade for executing stored procedures via {@link CallProcess#executeRaw}, kept as a backward-compatible entry point.Rationale: Etendo Wiki explicitly documents CallStoredProcedure and CallProcess as DAL utilities; when refactoring to a façade it helps maintainers to know which is canonical for new development and that behavior is now delegated.
| processCriteria.add(Restrictions.eq(Process.PROPERTY_PROCEDURE, processName)); | ||
|
|
||
| final Process process = (Process) processCriteria.uniqueResult(); | ||
|
|
There was a problem hiding this comment.
[RESOLVED] > [!WARNING]
Before calling uniqueResult(), explicitly check that the criteria returns at most one Process, and if more than one is found, throw an OBException mirroring the original behavior. This prevents a Hibernate NonUniqueResultException and preserves the previous controlled error handling.
+ if (processCriteria.list().size() > 1) {+ throw new OBException("More than one process found with procedure name " + processName);+ }+ final Process process = (Process) processCriteria.uniqueResult();Rationale: Per the earlier implementation, the logic guaranteed exactly one matching process and raised an OBException otherwise. Using uniqueResult() directly will throw a NonUniqueResultException on duplicates, which is less controlled and may not be localized or handled consistently by callers. Adding a size check maintains compatibility and avoids an unexpected runtime failure in case of data inconsistencies.
| @@ -62,230 +69,288 @@ public static synchronized void setInstance(CallProcess instance) { | |||
| CallProcess.instance = instance; | |||
| } | |||
|
|
|||
There was a problem hiding this comment.
[RESOLVED] > [!WARNING]
Remove the explicit "CALL" keyword from the Oracle branch and rely on the plain function/procedure call syntax, as the final query already uses "SELECT ... FROM DUAL" for non-returning cases. Using "CALL" together with a SELECT/DUAL wrapper will generate invalid SQL.
+ if (isOracle && !returnResults) {+ sb.append(name);+ } else {+ sb.append("SELECT ").append(name);+ }Rationale: The new buildSqlQuery method appends either "CALL" or "SELECT" and then, for Oracle and other DBs, may append "AS RESULT FROM DUAL". Combining "CALL" with a SELECT/DUAL suffix (e.g., "CALL MYPROC(...) AS RESULT FROM DUAL") is not valid Oracle syntax and will cause runtime SQL errors. Aligning with the previous CallStoredProcedure patterns where Oracle procedures are invoked without "CALL" avoids this issue.
| @@ -62,230 +69,288 @@ public static synchronized void setInstance(CallProcess instance) { | |||
| CallProcess.instance = instance; | |||
| } | |||
|
|
|||
There was a problem hiding this comment.
Suggestion
Avoid concatenating e.getMessage() into the exception text; just pass the original SQLException as the cause and keep a concise, stable outer message.
+ } catch (SQLException e) {+ throw new IllegalStateException("Error executing raw process " + procedureName, e);Rationale: Including the inner exception message in the outer message duplicates information exposed by the cause, can lead to overly verbose logs, and makes messages harder to localize or change. Wrapping with a stable message and the original cause is the typical Etendo/Java pattern and keeps logs clean while preserving full stack details.
| // Thread pool to manage background executions. | ||
| // Using a fixed pool prevents system resource exhaustion. | ||
| private final ExecutorService executorService = Executors.newFixedThreadPool(10); | ||
|
|
There was a problem hiding this comment.
Suggestion
Extract the hard‑coded thread pool size into a named constant. This aligns with Etendo patterns for tunable limits and makes future adjustments or externalization easier.
+ private static final int DEFAULT_THREAD_POOL_SIZE = 10;
+
+ private final ExecutorService executorService = Executors
+ .newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE);Rationale: Magic numbers in concurrency configuration make it harder to reason about resource usage and to tune behavior in different environments. Etendo code typically uses named constants for limits and thresholds.
| final String pInstanceId = pInstance.getId(); | ||
| final String processId = process.getId(); | ||
| final OBContext currentContext = OBContext.getOBContext(); | ||
|
|
There was a problem hiding this comment.
[RESOLVED] > [!WARNING]
Clone the current OBContext before passing it to another thread instead of reusing the same instance reference.
+ final OBContext currentContext = OBContext.getOBContext().clone();Rationale: OBContext is stored in a ThreadLocal and is not designed to be shared directly across threads. Passing the same instance reference to a different thread can lead to subtle concurrency issues if the context is mutated. Cloning the OBContext to hand off an independent copy is safer and better aligned with the documented ThreadLocal usage.
| // A. Context Hydration | ||
| // The new thread does not have the user session. We must set it manually. | ||
| OBContext.setOBContext(context); | ||
| OBContext.setAdminMode(); |
There was a problem hiding this comment.
[RESOLVED] > [!WARNING]
Instead of setting the OBContext instance directly, reconstruct the context in the worker thread using the user, role, client, org and language IDs from the original context.
+ OBContext.setOBContext(context.getUser(), context.getRole(), context.getCurrentClient().getId(),
+ context.getCurrentOrganization().getId(), context.getLanguage().getLanguage());Rationale: The OBContext API and documentation recommend creating the ThreadLocal context via the setOBContext(userId, roleId, clientId, orgId, lang) overload. This ensures correct internal initialization and avoids accidentally reusing internal references across threads.
| executeStandardProcedure(pInstance, process, doCommit); | ||
|
|
||
| // D. Commit Transaction | ||
| // In async threads, we are responsible for the transaction boundary. |
There was a problem hiding this comment.
[RESOLVED] > [!WARNING]
Honor the doCommit flag when handling the transaction in the async thread, similar to how DalBaseProcess and other Etendo process infrastructure respect this setting.
+ // D. Commit Transaction
+ // In async threads, we are responsible for the transaction boundary.
+ if (Boolean.TRUE.equals(doCommit)) {
+ OBDal.getInstance().commitAndClose();
+ } else {
+ OBDal.getInstance().getSession().clear();
+ }Rationale: Ignoring the doCommit flag changes the expected transactional behavior of processes that might deliberately control commit/rollback themselves. Respecting this flag avoids surprising commits and keeps behavior aligned with synchronous CallProcess/DalBaseProcess semantics.
| public static synchronized CallAsyncProcess getInstance() { | ||
| return instance; | ||
| } | ||
|
|
There was a problem hiding this comment.
Suggestion
Consider catching OBException separately or rethrowing it after updating the ProcessInstance, and use a proper logger instead of printing stack traces directly to stdout/stderr.
+ } catch (Exception e) {Rationale: Etendo guidelines favor using OBException to propagate business errors and using the logging framework (LogManager/Logger) instead of printStackTrace() for error reporting. This improves observability and avoids leaking raw stack traces to console-only logs.
| String msg = e.getMessage() != null ? e.getMessage() : e.toString(); | ||
| // Truncate to avoid DB errors if message is too long | ||
| if (msg.length() > 2000) msg = msg.substring(0, 2000); | ||
| pInstanceCtx.setErrorMsg("Async Error: " + msg); |
There was a problem hiding this comment.
Suggestion
Expand the one-line if into a multi-line block to align with common Etendo formatting and improve readability.
+ if (msg.length() > 2000) {
+ msg = msg.substring(0, 2000);
+ }Rationale: Multi-line if blocks with braces are less error-prone when the condition body evolves and better match the predominant style in Etendo core and modules.
etendobot
left a comment
There was a problem hiding this comment.
Changes requested by agent. Please resolve blocking issues.
|
|
||
| // 2. ASYNC PHASE: Submit to Executor | ||
| executorService.submit(() -> runInBackground(pInstanceId, processId, currentContext, doCommit)); | ||
|
|
There was a problem hiding this comment.
[RESOLVED] > [!WARNING]
Clone OBContext before handing it to another thread. Create a cloned context instance from OBContext.getOBContext() and pass that clone into the async runnable instead of the original ThreadLocal instance reference.
+ final OBContext currentContext = OBContext.getOBContext().clone();
+ executorService.submit(() -> runInBackground(pInstanceId, processId, currentContext, doCommit));Rationale: OBContext is implemented on top of ThreadLocal for per-thread isolation. Sharing the same OBContext instance across threads can cause subtle concurrency and security issues if the context state is mutated. Etendo recommendations for background/async processing emphasize creating an independent context per thread.
| * @see #call(String, List, List, boolean, boolean) | ||
| * Delegates to CallProcess. | ||
| * @param name: the stored procedure name. | ||
| * @param parameters: the parameters to pass to the stored procedure. |
There was a problem hiding this comment.
Suggestion
Remove the colon after the parameter name in the @param tags to align with standard Javadoc style used in Etendo (parameter name followed by a space and the description).
+ * @param name the stored procedure name.Rationale: Etendo’s JavaDoc and general Java conventions expect @param <name> <description> without punctuation after the parameter name, improving consistency and tooling compatibility.
| * Delegates to CallProcess. | ||
| * @param name: the stored procedure name. | ||
| * @param parameters: the parameters to pass to the stored procedure. | ||
| * @param types: the types of the parameters. |
There was a problem hiding this comment.
Suggestion
Drop the colon after parameters in the @param tag for consistency with Etendo Javadoc style.
+ * @param parameters the parameters to pass to the stored procedure.Rationale: Consistent Javadoc formatting (@param name description) makes documentation easier to read and aligns with the rest of the CallStoredProcedure class and Etendo guidelines.
| * @param name: the stored procedure name. | ||
| * @param parameters: the parameters to pass to the stored procedure. | ||
| * @param types: the types of the parameters. | ||
| * @return the result of the stored procedure. |
There was a problem hiding this comment.
Suggestion
Remove the colon after types in the Javadoc @param tag.
+ * @param types the types of the parameters.Rationale: Using the standard @param name description form keeps the Javadoc consistent with existing Etendo code and avoids small style discrepancies.
| * @param parameters: the parameters to pass to the stored procedure. | ||
| * @param types: the types of the parameters. | ||
| * @param doFlush: whether to flush the session before executing the stored procedure. | ||
| * @return the result of the stored procedure. |
There was a problem hiding this comment.
Suggestion
Remove the colon after doFlush in this @param tag for consistency with Etendo’s standard Javadoc format.
+ * @param doFlush whether to flush the session before executing the stored procedure.Rationale: Standardizing on @param name description improves readability and matches the pattern used in existing CallStoredProcedure Javadoc.
| * @param types: the types of the parameters. | ||
| * @param doFlush: whether to flush the session before executing the stored procedure. | ||
| * @param returnResults: whether to return the results of the stored procedure. | ||
| * @return the result of the stored procedure. |
There was a problem hiding this comment.
Suggestion
Drop the colon after returnResults in the Javadoc tag to follow the common @param format.
+ * @param returnResults whether to return the results of the stored procedure.Rationale: Aligns with the general Java and Etendo Javadoc style, keeping documentation consistent throughout the class.
| * @param parameters: the parameters to pass to the stored procedure. | ||
| * @param types: the types of the parameters. | ||
| * @return the result of the stored procedure. | ||
| */ |
There was a problem hiding this comment.
Suggestion
(Optional) Ensure the @return description explicitly mentions if null is ever returned; if not, current text is acceptable.
+ * @return the result of the stored procedure.Rationale: Etendo’s Javadoc guidelines recommend documenting when a method can return null so callers can handle it appropriately. If this method never returns null, you can keep the current wording.
| processCriteria.add(Restrictions.eq(Process.PROPERTY_PROCEDURE, processName)); | ||
|
|
||
| if (processCriteria.list().size() > 1) { | ||
| throw new OBException("More than one process found with procedure name " + processName); |
There was a problem hiding this comment.
Suggestion
Consider adding a brief comment explaining why the explicit size check is required before uniqueResult(), so future maintainers understand it is intentionally preserving the old OBException-based behavior instead of relying on Hibernate's NonUniqueResultException.
+ if (processCriteria.list().size() > 1) { // safeguard against NonUniqueResultException and preserve previous controlled behaviorRationale: The current logic is functionally correct and fixes the previous blocking issue, but the rationale (compatibility with the legacy behavior and avoiding uncontrolled NonUniqueResultException) is non-obvious. A short comment prevents future refactors from inadvertently removing this guard and reintroducing runtime failures.
| ps.setNull(index, Types.VARCHAR); | ||
| } else if (parameter instanceof Boolean) { | ||
| ps.setString(index, BooleanUtils.toBoolean((Boolean) parameter) ? "Y" : "N"); | ||
| } else if (parameter instanceof BaseOBObject) { |
There was a problem hiding this comment.
Suggestion
Use Boolean.TRUE.equals(parameter) instead of BooleanUtils.toBoolean((Boolean) parameter) here to avoid the unnecessary cast and external dependency, while still mapping true/false to "Y"/"N" safely and clearly.
+ ps.setString(index, Boolean.TRUE.equals(parameter) ? "Y" : "N");Rationale: The current code works but introduces an extra dependency and a cast that can be avoided. Using Boolean.TRUE.equals(parameter) keeps the semantics clear, avoids potential ClassCastException if this branch is ever reached with a non-Boolean type, and aligns with common patterns in the Etendo core when converting Booleans to Y/N flags.
| * </p> | ||
| * | ||
| * @author etendo | ||
| * @see CallStoredProcedure |
There was a problem hiding this comment.
Suggestion
Capitalize 'etendo' to 'Etendo'.
* @author EtendoRationale: The author name should be capitalized as 'Etendo' to follow branding and documentation standards.
|
|
||
| public static final String TEST_PROCEDURE = "test_procedure"; | ||
| public static final String RESULT_SHOULD_BE_DELEGATED_FROM_CALL_PROCESS = "Result should be delegated from CallProcess"; | ||
| private CallProcess mockCallProcess; |
There was a problem hiding this comment.
Suggestion
Change visibility from 'public' to 'private'.
private static final String RESULT_SHOULD_BE_DELEGATED_FROM_CALL_PROCESS = "Result should be delegated from CallProcess";Rationale: Constants used only within the test class should have 'private' visibility to adhere to encapsulation principles unless they are intended to be used by other classes.
| * | ||
| * @author mtaal | ||
| * @author @sebastianbarrozo | ||
| * @see ProcessInstance |
There was a problem hiding this comment.
Suggestion
Remove the '@' symbol from the author name.
* @author sebastianbarrozoRationale: The @ symbol is typically not used before the username in @author Javadoc tags. Removing it aligns with standard Javadoc conventions.
| */ | ||
| public static CallProcess getInstance() { | ||
| return instance.updateAndGet(v -> v == null ? new CallProcess() : v); | ||
| } |
There was a problem hiding this comment.
Suggestion
Consider using a more standard lazy initialization pattern with AtomicReference to ensure the constructor is only called when absolutely necessary.
CallProcess res = instance.get();
if (res == null) {
res = new CallProcess();
if (!instance.compareAndSet(null, res)) {
res = instance.get();
}
}
return res;Rationale: The updateAndGet method with a lambda that performs 'new CallProcess()' may create an instance even if it's not used (if the reference is updated by another thread simultaneously), although it is technically safe. A standard double-checked locking or a more traditional Atomic compareAndSet is often preferred for singleton instantiation to be explicit about creation. However, the current implementation is acceptable for a non-blocking suggestion.
| * the values used to populate the new context. | ||
| */ | ||
| private void hydrateContext(ContextValues contextValues) { | ||
| OBContext newContext = OBContext.getOBContext(); |
There was a problem hiding this comment.
Warning
Replace the manual setter-based hydration logic with a single call to 'OBContext.setOBContext' using the IDs captured in 'contextValues'.
private void hydrateContext(ContextValues contextValues) {
OBContext.setOBContext(contextValues.userId, contextValues.roleId, contextValues.clientId,
contextValues.organizationId, contextValues.languageId, contextValues.warehouseId);
}Rationale: Manually setting OBContext fields (User, Role, Client, Org) using setters is unsafe in Etendo. This approach bypasses critical internal initialization logic, such as calculating 'readableOrganizations', 'writableOrganizations', and 'entityAccessChecker', which are essential for DAL security and data filtering. The framework provides 'OBContext.setOBContext' overloads to safely initialize a thread-local context from IDs. This specific issue has been flagged in 14 previous reviews (UUIDs: 2721ca5e, c3952fd1, 77221531, 6efd61c9, 17fad40e, 46fd1d9b, 27892098, 66a5195b, cb735f12, cb3ee609, 797499cd, f48f6c0b, 09b05fa8, 3adfceef) and remains unfixed.
| * <p> | ||
| * This class is kept primarily for backward compatibility with older versions of the system. | ||
| * New code should prefer using {@link CallProcess} directly. | ||
| * </p> |
There was a problem hiding this comment.
Suggestion
The note is helpful; no change needed.
* New code should prefer using {@link CallProcess} directly.Rationale: This is a good documentation update to guide developers towards modern framework APIs (CallProcess) instead of legacy ones.
| * | ||
| * @author Openbravo | ||
| * @author etendo | ||
| * @see CallProcess |
There was a problem hiding this comment.
Suggestion
Maintain as is.
* @author etendoRationale: Adding the organization author is standard when modifying core files in the context of Etendo.
etendobot
left a comment
There was a problem hiding this comment.
Changes requested by agent. Please resolve blocking issues.
| * | ||
| * @author mtaal | ||
| * @author @sebastianbarrozo | ||
| * @see ProcessInstance |
There was a problem hiding this comment.
Suggestion
Remove the leading '@' from the username in the @author tag.
* @author sebastianbarrozoRationale: The @ symbol in '@sebastianbarrozo' is redundant when used with the @author JavaDoc tag and does not follow standard JavaDoc conventions.
| * @return the CallProcess instance. | ||
| */ | ||
| public static CallProcess getInstance() { | ||
| return instance.updateAndGet(v -> v == null ? new CallProcess() : v); |
There was a problem hiding this comment.
Suggestion
Consider using a more efficient singleton initialization pattern to avoid potential multiple instantiations of CallProcess during contention.
public static CallProcess getInstance() {
CallProcess res = instance.get();
if (res == null) {
instance.compareAndSet(null, new CallProcess());
res = instance.get();
}
return res;
}Rationale: Using updateAndGet with a lambda can lead to unnecessary object instantiation if multiple threads call getInstance() simultaneously, as the lambda might be executed multiple times even if only one result is stored. A double-checked locking pattern or simple compareAndSet is more efficient for singletons.
| public class CallStoredProcedureTest { | ||
|
|
||
| public static final String TEST_PROCEDURE = "test_procedure"; | ||
| public static final String RESULT_SHOULD_BE_DELEGATED_FROM_CALL_PROCESS = "Result should be delegated from CallProcess"; |
There was a problem hiding this comment.
Suggestion
Change the visibility of 'TEST_PROCEDURE' from public to private if it is not intended for use in other classes.
private static final String TEST_PROCEDURE = "test_procedure";Rationale: The constant is only used within this test class. Reducing visibility to private follows the principle of least privilege and keeps the API clean.
| * </p> | ||
| * | ||
| * @author etendo | ||
| * @see CallStoredProcedure |
There was a problem hiding this comment.
Suggestion
Capitalize 'Etendo' in the @author tag.
* @author EtendoRationale: Standardizing the author tag name ensures consistency across the codebase.
| * <p> | ||
| * This class is kept primarily for backward compatibility with older versions of the system. | ||
| * New code should prefer using {@link CallProcess} directly. | ||
| * </p> |
There was a problem hiding this comment.
Suggestion
The addition of the recommendation to use CallProcess is correct as it aligns with modern Etendo development patterns.
* New code should prefer using {@link CallProcess} directly.Rationale: Providing guidance on modern alternatives (CallProcess) within legacy classes is a recommended practice in Etendo to phase out older patterns.
| * | ||
| * @author Openbravo | ||
| * @author etendo | ||
| * @see CallProcess |
There was a problem hiding this comment.
Suggestion
Correctly added etendo as an author.
* @author etendoRationale: Updating authorship to reflect Etendo's maintenance or contribution is standard for modules or core classes modified within the Etendo ecosystem.
etendobot
left a comment
There was a problem hiding this comment.
Changes requested by agent. Please resolve blocking issues.
|
|
||
| /** | ||
| * Unit tests for the {@link CallStoredProcedure} class. | ||
| * <p> |
There was a problem hiding this comment.
Suggestion
The Javadoc improvement is good practice.
* Unit tests for the {@link CallStoredProcedure} class.Rationale: The addition of descriptive Javadoc improves maintainability and explains the purpose of the test suite.
| public class CallStoredProcedureTest { | ||
|
|
||
| public static final String TEST_PROCEDURE = "test_procedure"; | ||
| public static final String RESULT_SHOULD_BE_DELEGATED_FROM_CALL_PROCESS = "Result should be delegated from CallProcess"; |
There was a problem hiding this comment.
Suggestion
Extracting strings to constants is a good refactoring.
public static final String TEST_PROCEDURE = "test_procedure";Rationale: Using constants instead of magic strings in tests improves readability and makes the test suite easier to maintain.
|
|
||
| /** | ||
| * Sets up the test environment by mocking the {@link CallProcess} singleton. | ||
| * The original instance is stored to be restored after each test. |
There was a problem hiding this comment.
Suggestion
Good documentation of the test lifecycle.
* Sets up the test environment by mocking the {@link CallProcess} singleton.Rationale: Documentation for setup and teardown methods helps other developers understand how the test lifecycle is managed.
| * the values used to populate the new context. | ||
| */ | ||
| private void hydrateContext(ContextValues contextValues) { | ||
| OBContext newContext = OBContext.getOBContext(); |
There was a problem hiding this comment.
Warning
Replace the manual setter-based hydration logic in 'hydrateContext' with a single call to 'OBContext.setOBContext(...)' using the IDs stored in 'contextValues'.
private void hydrateContext(ContextValues contextValues) {
OBContext.setOBContext(contextValues.userId, contextValues.roleId, contextValues.clientId,
contextValues.organizationId, contextValues.languageId, contextValues.warehouseId);
}Rationale: Manually setting OBContext fields (User, Role, Client, Org) using setters in a new thread is unsafe. This approach bypasses critical internal initialization logic, such as calculating 'readableOrganizations', 'writableOrganizations', and 'entityAccessChecker', which are essential for DAL security and data filtering. The framework provides 'OBContext.setOBContext' overloads to safely initialize a thread-local context from IDs. This issue has been flagged in 15 previous reviews (UUIDs: 2721ca5e, c3952fd1, 77221531, 6efd61c9, 17fad40e, 46fd1d9b, 27892098, 66a5195b, cb735f12, cb3ee609, 797499cd, f48f6c0b, 09b05fa8, 3adfceef, 1b3a8be4) and remains unfixed.
| public class CallAsyncProcess extends CallProcess { | ||
|
|
||
| public static Logger log4j = LogManager.getLogger(CallAsyncProcess.class); | ||
|
|
There was a problem hiding this comment.
Suggestion
Change the logger to be 'private static final' and rename it to 'log' to follow standard patterns.
private static final Logger log = LogManager.getLogger(CallAsyncProcess.class);Rationale: Standard Etendo/Java practice uses 'private static final' for loggers, typically named 'log' or 'logger'. Making it 'public' and non-final is unnecessary and breaks encapsulation.
| * <p> | ||
| * This class is kept primarily for backward compatibility with older versions of the system. | ||
| * New code should prefer using {@link CallProcess} directly. | ||
| * </p> |
There was a problem hiding this comment.
Suggestion
The addition of the preference note is correct and helpful for developers.
* New code should prefer using {@link CallProcess} directly.Rationale: Providing guidance on preferred classes (CallProcess over CallStoredProcedure) is a documented best practice for DAL-based operations in Etendo.
| import java.util.Map; | ||
| import java.util.Properties; | ||
| import java.util.concurrent.atomic.AtomicReference; | ||
|
|
There was a problem hiding this comment.
Suggestion
The import is correct for the new thread-safe implementation.
import java.util.concurrent.atomic.AtomicReference;Rationale: Added support for thread-safe singleton management using AtomicReference.
| * | ||
| * @author mtaal | ||
| * @author @sebastianbarrozo | ||
| * @see ProcessInstance |
There was a problem hiding this comment.
Suggestion
Remove the '@' prefix from the author name in the Javadoc.
* @author sebastianbarrozoRationale: Standard Javadoc @author tags usually do not include the '@' symbol before the username.
| * @return the CallProcess instance. | ||
| */ | ||
| public static CallProcess getInstance() { | ||
| return instance.updateAndGet(v -> v == null ? new CallProcess() : v); |
There was a problem hiding this comment.
Suggestion
Consider using a standard lazy-initialization pattern to minimize object creation during contention.
public static CallProcess getInstance() {
CallProcess res = instance.get();
if (res == null) {
res = new CallProcess();
if (!instance.compareAndSet(null, res)) {
res = instance.get();
}
}
return res;
}Rationale: While updateAndGet is thread-safe, it might execute the 'new CallProcess()' multiple times in a high-concurrency race before discarding the extra instances. A double-checked locking or compareAndSet approach is more conventional for singletons to avoid unnecessary object allocation.
| * Calls a process by its procedure name with explicit commit control. | ||
| * | ||
| * @param processName | ||
| * the procedure name defined in AD_Process. |
There was a problem hiding this comment.
Suggestion
Reformat Javadoc parameters for better consistency with the existing codebase.
* @param processName the procedure name defined in AD_Process.
* @param recordID the record ID associated with the execution (optional).
* @param parameters a map of parameters to be injected into AD_PInstance_Para.
* @param doCommit explicit commit flag (if supported by the SP).
* @return the result ProcessInstance.Rationale: Standard Etendo/Openbravo Javadoc typically keeps the description on the same line as the tag or follows a more compact indentation style.
etendobot
left a comment
There was a problem hiding this comment.
Changes requested by agent. Please resolve blocking issues.
ce0d8f8 to
cbbbf94
Compare
No description provided.