All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.yupiik.batch.runtime.tracing.ExecutionTracer Maven / Gradle / Ivy

/*
 * Copyright (c) 2021 - Yupiik SAS - https://www.yupiik.com
 * Licensed 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 io.yupiik.batch.runtime.tracing;

import io.yupiik.batch.runtime.batch.builder.Executable;
import io.yupiik.batch.runtime.batch.builder.RunConfiguration;
import io.yupiik.batch.runtime.sql.SQLBiConsumer;
import io.yupiik.batch.runtime.sql.SQLSupplier;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.time.Clock;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ExecutionTracer extends BaseExecutionTracer {
    private final SQLSupplier dataSource;

    public ExecutionTracer(final SQLSupplier dataSource,
                           final String batchName, final Clock clock) {
        super(batchName, clock);
        this.dataSource = dataSource;
    }

    @Override
    protected void save(final JobExecution job, final List steps) {
        try (final var connection = dataSource.get();
             final var statement = connection.prepareStatement("" +
                     "INSERT INTO BATCH_JOB_EXECUTION_TRACE" +
                     " (id, name, status, comment, started, finished) VALUES " +
                     " (?, ?, ?, ?, ?, ?)")) {
            statement.setString(1, job.id());
            statement.setString(2, job.name());
            statement.setString(3, job.status() == null ? "-" : job.status().name());
            statement.setString(4, job.comment());
            statement.setObject(5, job.started());
            statement.setObject(6, job.finished());
            statement.executeUpdate();
            if (!connection.getAutoCommit()) {
                connection.commit();
            }
        } catch (final SQLException throwables) {
            throw new IllegalStateException(throwables);
        }
        final var stepsIt = steps.iterator();
        if (!stepsIt.hasNext()) {
            return;
        }

        final var insert = new SQLBiConsumer.Batched() {
            @Override
            protected PreparedStatement createStatement(final Connection connection) throws SQLException {
                return connection.prepareStatement("" +
                        "INSERT INTO BATCH_STEP_EXECUTION_TRACE" +
                        " (id, job_id, name, status, comment, started, finished, previous_id) VALUES" +
                        " (?, ?, ?, ?, ?, ?, ?, ?)");
            }

            @Override
            protected void doAccept(final StepExecution row) throws SQLException {
                statement.setString(1, row.id());
                statement.setString(2, job.id());
                statement.setString(3, row.name());
                if (row.status() == null) {
                    statement.setNull(4, Types.VARCHAR);
                } else {
                    statement.setString(4, row.status().name());
                }
                statement.setString(5, row.comment());
                statement.setObject(6, row.started());
                statement.setObject(7, row.finished());
                if (row.previous() == null) {
                    statement.setNull(8, Types.VARCHAR);
                } else {
                    statement.setString(8, row.previous());
                }
            }
        };
        final int commitInterval = 10; // likely sufficient in one iteration since #steps < 10 in general
        try (final var connection = dataSource.get()) {
            while (stepsIt.hasNext()) {
                final boolean autoCommit = connection.getAutoCommit();
                connection.setAutoCommit(false);
                try {
                    for (int i = 0; i < commitInterval && stepsIt.hasNext(); i++) {
                        final var row = stepsIt.next();
                        insert.accept(connection, row);
                    }
                    insert.close();
                    connection.commit();
                } catch (final RuntimeException | SQLException ex) {
                    onException(connection, ex);
                    throw ex;
                } catch (final Exception ex) {
                    onException(connection, ex);
                    throw new IllegalStateException(ex);
                } finally {
                    connection.setAutoCommit(autoCommit);
                }
            }
        } catch (final RuntimeException re) {
            throw re;
        } catch (final SQLException sqle) {
            throw new IllegalStateException(sqle);
        }
    }

    private void onException(final Connection connection, final Exception ex) throws SQLException {
        connection.rollback();
        Logger.getLogger(getClass().getName()).log(Level.SEVERE, ex.getMessage(), ex);
    }

    public RunConfiguration chilRunConfiguration() {
        final var configuration = new RunConfiguration();
        configuration.setElementExecutionWrapper(e -> (c, r) -> Executable.Result.class.cast(traceStep(c, e, r)));
        return configuration;
    }

    public static RunConfiguration runConfiguration(final SQLSupplier dataSource, final String batch,
                                                    final Clock clock) {
        return trace(new RunConfiguration(), dataSource, batch, clock);
    }

    public static RunConfiguration trace(final RunConfiguration configuration,
                                         final SQLSupplier dataSource, final String batch, final Clock clock) {
        final var tracer = new ExecutionTracer(dataSource, batch, clock);
        configuration.setExecutionWrapper(tracer::traceExecution);
        configuration.setElementExecutionWrapper(e -> (c, r) -> Executable.Result.class.cast(tracer.traceStep(c, e, r)));
        return configuration;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy