Class OperationResult

  • All Implemented Interfaces:
    Visitable<OperationResult>, OperationResultBuilder, DebugDumpable, ShortDumpable, Serializable, Cloneable

    public class OperationResult
    extends Object
    implements Serializable, DebugDumpable, ShortDumpable, Cloneable, OperationResultBuilder, Visitable<OperationResult>
    Provides rich information about an operation being executed; mainly for the sake of error reporting and functional/performance troubleshooting. == Information Collected There is a lot of information collected, but the following properties are the most important: - result *status* (OperationResultStatus): success, partial/fatal error, warning, ..., along with an optional *message* and *Java exception*, - operation invocation *parameters*, *return value(s)*, and sometimes information about the execution *context* (e.g. implementation class name), - *performance-related information*, like start/end timestamps, or duration (for performance diagnostics), - TraceType records (for troubleshooting), - *logfile lines* produced during the operation execution (for troubleshooting). The structure is hierarchical, i.e. an OperationResult instance contains a set of results of inner operations. == Typical Uses - This object can be used by GUI to display smart (and interactive) error information. - It can also be used by the client code (Java or REST) to detect deeper problems in the invocations, retry or otherwise compensate for the errors or decide how severe the error was and whether it is possible to proceed. - The performance information collected is recorded in tasks (see OperationsPerformanceInformationType) and shown in GUI. See also OperationsPerformanceMonitorImpl class. - The functional and performance data collected are used for (experimental) link:https://docs.evolveum.com/midpoint/reference/diag/troubleshooting/troubleshooting-with-traces/[troubleshooting with traces]. == Developer's Guidelines In order to ensure that all necessary information is correctly captured, a developer of any method that writes into OperationResult instances has to follow a couple of basic principles: === Principle 1: Correct Closure Any operation result created has to be correctly _closed_. This means that its status should be changed from the initial OperationResultStatus.UNKNOWN value to a more specific one. (Otherwise, a run-time exception is thrown in cleanupResult(Throwable) method.) Also, to ensure correct fulfillment of the other duties, recordEnd() method has be - directly or indirectly - called as well (see below). === Principle 2: Single Result Per Thread At any instant, in any thread, there should be at most one OperationResult "active", i.e. opened by createSubresult(String) or its variants (e.g. createMinorSubresult(String), subresult(String), ...), and not yet closed nor "superseded" by creating its own child result. This is to ensure that logfile lines will be correctly captured, if tracing is enabled. It is also required for correct collection of performance data. === Principle 3: Opening-Closure Pairing Because the operation result is used also to troubleshoot performance issues, it should be clear where (in the code) the result is created and where it is closed. When is result closed, anyway? The result is automatically closed when the status is recorded: either _explicitly_ (like in recordSuccess()) or _implicitly_ (e.g. in computeStatus()). All those methods invoke recordEnd() that does all the magic related to performance information capturing, log records capturing, and (occasionally used) method call logging. To ensure easy, clear and unambiguous interpretation of the performance data, the developer should make sure that for each OperationResult created, it is obvious _where_ the result is closed. The best way how to ensure this is to create the result at the beginning (or near the beginning) of a related method, and close it - including writing the status - at the end (or near the end) of the same method. No recording of the status should be done between these points. If there's a need to set the value of status somewhere during the operation execution (assuming that there's non-negligible processing after that point), setStatus(OperationResultStatus) method or its variants (like setSuccess()) can be used. These fill-in status field without closing the whole operation result. === Note: Recording Exceptions The Correct Closure Principle (#1) dictates that the operation result has to be correctly closed even in the case of exception occurring. That is why we usually create a "try-catch" block for the code covered by a single operation result. Traditionally, the recordFatalError(Throwable) call was put into the `catch` code. However, there may be situations where a different handling is required: . If the exception is non-fatal or even completely benign. This may be the case of e.g. expected ("allowed") object-not-found conditions. See ObjectNotFoundException.getSeverity(). . If the exception was processed by other means. For example, a custom error message was already provided. To handle these cases, recordException(Throwable) should be used instead of recordFatalError(Throwable). The difference is that the former checks the exceptionRecorded flag to see if the exception was already processed. See also markExceptionRecorded(). NOTE: This mechanism is *experimental*. === Suggested Use Stemming from the above, the following can be seen as a suggested way how to use the operation result: [source,java] ---- private static final OP_SOME_METHOD = ThisClass.class.getName() + ".someMethod"; void someMethod(String param1, Object param2, OperationResult parentResult) throws SomeException { OperationResult result = parentResult.subresult(OP_SOME_METHOD) .addParam("param1", param1) .addArbitraryObjectAsParam("param2", param2) .build(); try { // ... some meat here ... } catch (SomeException | RuntimeException e) { result.recordException(e); throw e; } finally { result.close(); } } ---- Note that the close() method (a newer form of legacy computeStatusIfUnknown()) automatically computes the result based on subresults (assuming success if there's no indication of a failure). In theory we could put it inside the `try` block (because `recordFatalError` effectively closes the result as well), but using `finally` is perhaps more explicit. It may be also more future-proof if we would decide to add some extra functionality right into close() method itself.
    Author:
    lazyman, Radovan Semancik
    See Also:
    Serialized Form