PipesResult.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.tika.pipes.api;

import java.io.Serializable;

import org.apache.tika.pipes.api.emitter.EmitData;

public record PipesResult(RESULT_STATUS status, EmitData emitData, String message) implements Serializable {

    /**
     * High-level categorization of result statuses.
     * <p>
     * Categories help distinguish between different types of failures and successes,
     * allowing infrastructure to decide how to handle each case:
     * <ul>
     *   <li>{@link #FATAL} - System cannot continue, must be fixed and restarted</li>
     *   <li>{@link #INITIALIZATION_FAILURE} - Component failed to initialize, might be transient</li>
     *   <li>{@link #TASK_EXCEPTION} - This task failed, but other tasks may succeed</li>
     *   <li>{@link #PROCESS_CRASH} - Forked process crashed, auto-restart and continue</li>
     *   <li>{@link #SUCCESS} - Processing completed successfully (possibly with warnings)</li>
     * </ul>
     */
    public enum CATEGORY {
        /** Fatal system error - cannot continue, must be fixed and restarted */
        FATAL,

        /** Component initialization failed - processing should stop, might be transient */
        INITIALIZATION_FAILURE,

        /** Task-level exception - this task failed, log and continue with next task */
        TASK_EXCEPTION,

        /** Forked process crashed due to OOM, timeout, or other system failure - auto-restart */
        PROCESS_CRASH,

        /** Processing completed successfully (possibly with warnings) */
        SUCCESS
    }

    public enum RESULT_STATUS {
        // Fatal errors - system cannot continue
        FAILED_TO_INITIALIZE(CATEGORY.FATAL),

        // Initialization failures - component failed to start, might be transient
        FETCHER_INITIALIZATION_EXCEPTION(CATEGORY.INITIALIZATION_FAILURE),
        EMITTER_INITIALIZATION_EXCEPTION(CATEGORY.INITIALIZATION_FAILURE),
        CLIENT_UNAVAILABLE_WITHIN_MS(CATEGORY.INITIALIZATION_FAILURE),

        // Task exceptions - this task failed, continue with next
        FETCH_EXCEPTION(CATEGORY.TASK_EXCEPTION),
        EMIT_EXCEPTION(CATEGORY.TASK_EXCEPTION),
        FETCHER_NOT_FOUND(CATEGORY.TASK_EXCEPTION),
        EMITTER_NOT_FOUND(CATEGORY.TASK_EXCEPTION),

        // Process crashes - forked process died, auto-restart
        OOM(CATEGORY.PROCESS_CRASH),
        TIMEOUT(CATEGORY.PROCESS_CRASH),
        UNSPECIFIED_CRASH(CATEGORY.PROCESS_CRASH),

        // Success states
        EMPTY_OUTPUT(CATEGORY.SUCCESS),
        PARSE_SUCCESS(CATEGORY.SUCCESS),
        PARSE_SUCCESS_WITH_EXCEPTION(CATEGORY.SUCCESS),
        PARSE_EXCEPTION_NO_EMIT(CATEGORY.SUCCESS),
        EMIT_SUCCESS(CATEGORY.SUCCESS),
        EMIT_SUCCESS_PARSE_EXCEPTION(CATEGORY.SUCCESS),
        EMIT_SUCCESS_PASSBACK(CATEGORY.SUCCESS);


        private final CATEGORY category;

        RESULT_STATUS(CATEGORY category) {
            this.category = category;
        }

        /**
         * Gets the high-level category for this result status.
         *
         * @return the category (FATAL, INITIALIZATION_FAILURE, TASK_EXCEPTION, PROCESS_CRASH, or SUCCESS)
         */
        public CATEGORY getCategory() {
            return category;
        }

        /**
         * Checks if this status represents a fatal error.
         * <p>
         * Fatal errors mean the system cannot continue and must be fixed and restarted.
         *
         * @return true if this is a fatal error
         */
        public boolean isFatal() {
            return category == CATEGORY.FATAL;
        }

        /**
         * Checks if this status represents an initialization failure.
         * <p>
         * Initialization failures occur when a component (fetcher, emitter, client)
         * fails to start. These might be transient (e.g., network issues) or require
         * configuration fixes.
         *
         * @return true if a component failed to initialize
         */
        public boolean isInitializationFailure() {
            return category == CATEGORY.INITIALIZATION_FAILURE;
        }

        /**
         * Checks if this status represents a task-level exception.
         * <p>
         * Task exceptions indicate this specific task failed, but other tasks
         * may succeed. Processing can continue with the next task.
         *
         * @return true if this task failed
         */
        public boolean isTaskException() {
            return category == CATEGORY.TASK_EXCEPTION;
        }

        /**
         * Checks if this status represents a process crash (OOM, timeout, etc.).
         * <p>
         * Process crashes are system-level failures where the forked process
         * terminated abnormally. The process will be auto-restarted and
         * processing can continue.
         *
         * @return true if the forked process crashed
         */
        public boolean isProcessCrash() {
            return category == CATEGORY.PROCESS_CRASH;
        }

        /**
         * Checks if this status represents successful processing.
         * <p>
         * Success includes normal completion as well as cases where
         * processing completed with warnings (e.g., PARSE_SUCCESS_WITH_EXCEPTION).
         *
         * @return true if processing completed successfully (possibly with warnings)
         */
        public boolean isSuccess() {
            return category == CATEGORY.SUCCESS;
        }

        public static RESULT_STATUS lookup(int b) {
            if (b < 1) {
                throw new IllegalArgumentException("bad result value: " + b);
            }
            int ordinal = b - 1;
            if (ordinal >= RESULT_STATUS.values().length) {
                throw new IllegalArgumentException("ordinal > than array length? " + ordinal);
            }
            return RESULT_STATUS.values()[ordinal];
        }

        public byte getByte() {
            return (byte) (ordinal() + 1);
        }
    }

    public PipesResult(RESULT_STATUS status) {
        this(status, null, null);
    }

    public PipesResult(RESULT_STATUS status, EmitData emitData) {
        this(status, emitData, null);
    }

    public PipesResult(RESULT_STATUS status, String message) {
        this(status, null, message);
    }

    /**
     * Gets the high-level category for this result.
     *
     * @return the category (FATAL, INITIALIZATION_FAILURE, TASK_EXCEPTION, PROCESS_CRASH, or SUCCESS)
     */
    public CATEGORY getCategory() {
        return status.getCategory();
    }

    /**
     * Checks if this result represents a fatal error.
     * <p>
     * Fatal errors mean the system cannot continue and must be fixed and restarted.
     *
     * @return true if this is a fatal error
     */
    public boolean isFatal() {
        return status.isFatal();
    }

    /**
     * Checks if this result represents an initialization failure.
     * <p>
     * Initialization failures occur when a component (fetcher, emitter, client)
     * fails to start. These might be transient (e.g., network issues) or require
     * configuration fixes.
     *
     * @return true if a component failed to initialize
     */
    public boolean isInitializationFailure() {
        return status.isInitializationFailure();
    }

    /**
     * Checks if this result represents a task-level exception.
     * <p>
     * Task exceptions indicate this specific task failed, but other tasks
     * may succeed. Processing can continue with the next task.
     *
     * @return true if this task failed
     */
    public boolean isTaskException() {
        return status.isTaskException();
    }

    /**
     * Checks if this result represents a process crash (OOM, timeout, etc.).
     * <p>
     * Process crashes are system-level failures where the forked process
     * terminated abnormally. The process will be auto-restarted and
     * processing can continue.
     *
     * @return true if the forked process crashed
     */
    public boolean isProcessCrash() {
        return status.isProcessCrash();
    }

    /**
     * Checks if this result represents successful processing.
     * <p>
     * Success includes normal completion as well as cases where
     * processing completed with warnings (e.g., PARSE_SUCCESS_WITH_EXCEPTION).
     *
     * @return true if processing completed successfully (possibly with warnings)
     */
    public boolean isSuccess() {
        return status.isSuccess();
    }

}