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

org.jboss.pnc.bifrost.endpoint.RestImpl Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2020-2022 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.jboss.pnc.bifrost.endpoint;

import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.opentelemetry.extension.annotations.WithSpan;

import org.jboss.pnc.api.bifrost.dto.Line;
import org.jboss.pnc.api.bifrost.dto.MetaData;
import org.jboss.pnc.api.bifrost.enums.Direction;
import org.jboss.pnc.api.bifrost.enums.Format;
import org.jboss.pnc.api.bifrost.rest.Bifrost;
import org.jboss.pnc.bifrost.common.DateUtil;
import org.jboss.pnc.bifrost.common.Reference;
import org.jboss.pnc.bifrost.common.scheduler.Subscription;
import org.jboss.pnc.bifrost.common.scheduler.TimeoutExecutor;
import org.jboss.pnc.bifrost.endpoint.provider.DataProvider;
import org.jboss.pnc.common.security.Md5;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.ws.rs.Path;
import javax.ws.rs.ServerErrorException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * @author Matej Lazar
 */
@Path("/")
public class RestImpl implements Bifrost {

    private static final String className = RestImpl.class.getName();

    private static Logger logger = LoggerFactory.getLogger(RestImpl.class);

    @Inject
    DataProvider dataProvider;

    private Map probeExecutor = new ConcurrentHashMap<>();

    @Inject
    MeterRegistry registry;

    private Counter errCounter;
    private Counter warnCounter;

    @PostConstruct
    void initMetrics() {
        errCounter = registry.counter(className + ".error.count");
        warnCounter = registry.counter(className + ".warning.count");
    }

    @Timed
    @Override
    public Response getAllLines(
            String matchFilters,
            String prefixFilters,
            Line afterLine,
            Direction direction,
            Format format,
            Integer maxLines,
            Integer batchSize,
            Integer batchDelay,
            boolean follow,
            String timeoutProbeString) {

        validateAndFixInputDate(afterLine);

        ArrayBlockingQueue> queue = new ArrayBlockingQueue(1024);

        Runnable addEndOfDataMarker = () -> {
            try {
                queue.offer(Optional.empty(), 5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                errCounter.increment();
                logger.error("Cannot add end of data marker.", e);
            }
        };

        Subscription subscription = new Subscription(addEndOfDataMarker);

        StreamingOutput stream = outputStream -> {

            Reference timeoutProbeTask = new Reference<>();
            if (follow && timeoutProbeString != null && !timeoutProbeString.equals("")) {
                TimeoutExecutor timeoutExecutor = new TimeoutExecutor(getExecutorService());
                Runnable sendProbe = () -> {
                    Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream));
                    try {
                        writer.write(timeoutProbeString);
                        writer.flush();
                    } catch (IOException e) {
                        timeoutProbeTask.get().cancel();
                        warnCounter.increment();
                        logger.warn("Cannot send connection probe, client might closed the connection.", e);
                        complete(subscription, outputStream);
                    }
                };
                timeoutProbeTask.set(timeoutExecutor.submit(sendProbe, 15000, TimeUnit.MILLISECONDS));
            }

            while (true) {
                try {
                    Optional maybeLine = queue.poll(30, TimeUnit.MINUTES);
                    if (maybeLine.isPresent()) {
                        Line line = maybeLine.get();
                        String message = line.asString(format);
                        logger.trace("Sending line: " + message);

                        Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream));
                        writer.write(handleNewLine(message));
                        writer.flush();

                        if (line.isLast() && follow == false) { // when follow is true, the connection must be
                                                                // terminated from
                                                                // the client side
                            timeoutProbeTask.ifPresent(t -> t.cancel());
                            complete(subscription, outputStream);
                            break;
                        }
                        timeoutProbeTask.ifPresent(t -> t.update());
                    } else { // empty line indicating end of results
                        logger.info("Closing connection, no results.");
                        timeoutProbeTask.ifPresent(t -> t.cancel());
                        complete(subscription, outputStream);
                        break;
                    }
                } catch (IOException e) {
                    warnCounter.increment();
                    logger.warn(
                            "Cannot write output. Client might closed the connection. Unsubscribing ... "
                                    + e.getMessage());
                    timeoutProbeTask.ifPresent(t -> t.cancel());
                    complete(subscription, outputStream);
                    break;
                } catch (InterruptedException e) {
                    errCounter.increment();
                    logger.error("Cannot read from queue.", e);
                    timeoutProbeTask.ifPresent(t -> t.cancel());
                    complete(subscription, outputStream);
                    break;
                }
            }
        };

        fillQueueWithLines(
                matchFilters,
                prefixFilters,
                afterLine,
                maxLines,
                batchSize,
                batchDelay,
                follow,
                queue,
                addEndOfDataMarker,
                subscription);
        return Response.ok(stream).build();
    }

    private String handleNewLine(String message) {
        if (message == null || message.isEmpty()) {
            return "\n";
        }

        if (message.endsWith("\n")) {
            return message;
        }

        return message.concat(System.lineSeparator());
    }

    @Timed
    @WithSpan()
    protected void fillQueueWithLines(
            String matchFilters,
            String prefixFilters,
            Line afterLine,
            Integer maxLines,
            Integer batchSize,
            Integer batchDelay,
            boolean follow,
            ArrayBlockingQueue> queue,
            Runnable addEndOfDataMarker,
            Subscription subscription) {
        int[] receivedLines = { 0 };
        Consumer onLine = line -> {
            try {
                if (line != null) {
                    logger.trace("Adding line to output queue: " + line.asString());
                    queue.offer(Optional.of(line), 5, TimeUnit.SECONDS); // TODO
                    receivedLines[0]++;

                    if (maxLines != null && receivedLines[0] >= maxLines) {
                        logger.debug("Received max lines, unsubscribing ...");
                        addEndOfDataMarker.run();
                        dataProvider.unsubscribe(subscription);
                    }
                } else {
                    logger.debug("Received null line.");
                }

                if (follow == false && (line == null || line.isLast())) {
                    logger.debug("Received last line or no results, unsubscribing and closing ...");
                    // signal connection close
                    addEndOfDataMarker.run();
                    dataProvider.unsubscribe(subscription);
                }
            } catch (Exception e) {
                warnCounter.increment();
                logger.warn("Unsubscribing due to the exception.", e);
                addEndOfDataMarker.run();
                dataProvider.unsubscribe(subscription);
            }
        };
        dataProvider.subscribe(
                matchFilters,
                prefixFilters,
                Optional.ofNullable(afterLine),
                onLine,
                subscription,
                Optional.ofNullable(maxLines),
                Optional.ofNullable(batchSize),
                Optional.ofNullable(batchDelay));
    }

    private ScheduledThreadPoolExecutor getExecutorService() {
        return probeExecutor.computeIfAbsent("INSTANCE", k -> new ScheduledThreadPoolExecutor(1));
    }

    private void complete(Subscription subscription, OutputStream outputStream) {
        dataProvider.unsubscribe(subscription);
        try {
            outputStream.close();
        } catch (IOException e) {
            warnCounter.increment();
            logger.warn("Cannot close output stream.", e);
        }
    }

    @Override
    public List getLines(
            String matchFilters,
            String prefixFilters,
            Line afterLine,
            Direction direction,
            Integer maxLines,
            Integer batchSize) throws IOException {
        validateAndFixInputDate(afterLine);

        List lines = new ArrayList<>();
        Consumer onLine = lines::add;
        dataProvider.get(
                matchFilters,
                prefixFilters,
                Optional.ofNullable(afterLine),
                direction,
                Optional.ofNullable(maxLines),
                Optional.ofNullable(batchSize),
                onLine);
        return lines;
    }

    @Override
    public MetaData getMetaData(
            String matchFilters,
            String prefixFilters,
            Line afterLine,
            Direction direction,
            Integer maxLines,
            Integer batchSize) throws IOException {

        validateAndFixInputDate(afterLine);

        Md5 md5;
        try {
            md5 = new Md5();
        } catch (NoSuchAlgorithmException e) {
            errCounter.increment();
            throw new ServerErrorException(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR);
        }

        Consumer onLine = line -> {
            try {
                md5.add(line.getMessage());
            } catch (UnsupportedEncodingException e) {
                errCounter.increment();
                throw new ServerErrorException(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR);
            }
        };
        dataProvider.get(
                matchFilters,
                prefixFilters,
                Optional.ofNullable(afterLine),
                direction,
                Optional.ofNullable(maxLines),
                Optional.ofNullable(batchSize),
                onLine);

        return new MetaData(md5.digest());
    }

    public static void validateAndFixInputDate(Line afterLine) {
        if (afterLine != null) {
            afterLine.setTimestamp(DateUtil.validateAndFixInputDate(afterLine.getTimestamp()));
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy