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

nl.vpro.api.client.utils.MediaRestClientUtils Maven / Gradle / Ivy

package nl.vpro.api.client.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.time.Instant;
import java.util.*;
import java.util.function.Supplier;

import javax.validation.constraints.NotNull;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;

import org.apache.commons.io.IOUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import com.google.common.collect.Lists;

import nl.vpro.api.client.frontend.NpoApiClients;
import nl.vpro.api.rs.v3.media.MediaRestService;
import nl.vpro.api.rs.v3.subtitles.SubtitlesRestService;
import nl.vpro.domain.api.*;
import nl.vpro.domain.api.media.*;
import nl.vpro.domain.media.*;
import nl.vpro.domain.subtitles.Subtitles;
import nl.vpro.domain.subtitles.SubtitlesId;
import nl.vpro.jackson2.JsonArrayIterator;
import nl.vpro.logging.simple.Level;
import nl.vpro.poms.shared.Headers;
import nl.vpro.util.*;

import static nl.vpro.api.client.utils.ChangesFeedParameters.changesParameters;
import static nl.vpro.logging.simple.Slf4jSimpleLogger.slf4j;

/**
 * @author Michiel Meeuwissen
 * @since 1.1
 */
@Slf4j
public class MediaRestClientUtils {

    /**
     * Similar to the v1 call for easier migration
     */
    public static MediaForm getRecentPlayablePrograms(AVType avType) {

        MediaFormBuilder formBuilder = MediaFormBuilder.form();

        if (avType != null) {
            formBuilder.avTypes(Match.MUST, avType);
        }

        List excludedTypes = new ArrayList<>();
        for (GroupType type : GroupType.values()) {
            excludedTypes.add(type.getMediaType());
        }
        excludedTypes.add(ProgramType.TRACK.getMediaType());
        excludedTypes.add(SegmentType.SEGMENT.getMediaType());

        MediaForm form = formBuilder.types(Match.NOT, excludedTypes.toArray(new MediaType[0])).build();
        form.addSortField(new MediaSortOrder(MediaSortField.sortDate, Order.DESC));

        return form;
    }

    public static List adapt(final MediaSearchResult result) {
        return new AbstractList<>() {

            @Override
            public MediaObject get(int index) {
                SearchResultItem object = result.getItems().get(index);
                return object.getResult();
            }

            @Override
            public int size() {
                return result.getSize();
            }
        };
    }

    public static MediaObject loadOrNull(MediaRestService restService, String id) {
        /*return wrapForOrNull(
            () -> restService.load(id, null, null),
            () -> id
        );*/
        return restService.loadMultiple(new IdList(id), null, null).getItems().get(0).getResult();
    }



    public static Subtitles loadOrNull(SubtitlesRestService restService, String mid, Locale language) throws IOException {
        return wrapForOrNull(
            () -> restService.get(mid, language),
            () -> SubtitlesId.builder().mid(mid).language(language).build().toString()
        );

    }

    /**
     * Converts some exceptions to 'null', most noticably {@link NotFoundException}
     *
     * @param supplier The action to perform
     * @param id Id to use for logging if exceptions happen
     */
    private static  T wrapForOrNull(
        Supplier supplier,
        Supplier id
    ) throws IOException {
        try {
            return supplier.get();
        } catch (NotFoundException nfe) {
            // not even log, this is not errorneous
            return null;
        } catch (ProcessingException pe) {
            unwrapIO(pe);
            log.warn(id.get() + " " + pe.getMessage());
            return null;
        } catch (RuntimeException ise) {
            // Completely unexpected, this should remain to be an exception!
            throw ise;
        } catch (Exception e) {
            log.error(id.get() + " " + e.getClass().getName() + " " + e.getMessage());
            return null;
        }
    }


    public static void unwrapIO(ProcessingException pe) throws IOException {
        Throwable t = pe.getCause();
        if (t instanceof IOException) {
            throw (IOException) t;
        }
    }

    public static MediaObject[] load(MediaRestService restService, String... ids) {
        return loadWithMultiple(restService, ids);
        //loadWithSearch(restService, ids); // doesn't preserve order/duplicates, probable slower too.
    }

    private static MediaObject[] loadWithMultiple(MediaRestService restService, String... ids) {
        List result = new ArrayList<>(ids.length);
        if (ids.length > 0) {
            for (List idList : Lists.partition(Arrays.asList(ids), 240)) {
                MultipleMediaResult mediaResult = restService.loadMultiple(new IdList(idList), null, null);
                result.addAll(Lists.transform(mediaResult.getItems(), MultipleEntry::getResult));
            }
        }
        return result.toArray(new MediaObject[0]);
    }
    // unused for now
    private static MediaObject[] loadWithSearch(MediaRestService restService, String... ids) {
        List result = new ArrayList<>(ids.length);

        /*
         * Calling restService.find with max > Constants.MAX_RESULTS gets you a BadRequestException,
         * so the list of ids is partitioned first and if needed multiple find calls are executed...
         */
        for (List idList : Lists.partition(Arrays.asList(ids), Constants.MAX_RESULTS)) {
            String[] partitionedIds = idList.toArray(new String[0]);
            MediaForm mediaForm = MediaFormBuilder.form().mediaIds(partitionedIds).build();
            MediaSearchResult mediaSearchResult = restService.find(mediaForm, null, null, 0L, idList.size());
            result.addAll(adapt(mediaSearchResult));
        }

        return result.toArray(new MediaObject[0]);
    }

    @Deprecated
    public static JsonArrayIterator changes(MediaRestService restService, String profile, long since, Order order, Integer max) throws IOException {
        try {
            final InputStream inputStream = toInputStream(restService.changes(profile, null, since, null, order, max, null, Tail.ALWAYS, null));
            return new JsonArrayIterator<>(
                inputStream,
                MediaChange.class,
                () -> IOUtils.closeQuietly(inputStream)
            );
        } catch (ProcessingException pi) {
            Throwable t = pi.getCause();
            throw new RuntimeException(t.getMessage(), t);
        }
    }

    public static JsonArrayIterator changes(
        @NotNull MediaRestService restService,
        @Nullable String profile,
        @NonNull Instant since,
        @Nullable String mid,
        @Nullable Order order,
        @Nullable Integer max,
        @Nullable Deletes deletes,
        @Nullable Tail tail) throws IOException {
        return changes(restService,
            changesParameters()
                .profile(profile)
                .mediaSince(MediaSince.of(since, mid))
                .order(order)
                .max(max)
                .deletes(deletes)
                .tail(tail)
                .build()
        );
    }



    public static JsonArrayIterator changes(
        @NotNull MediaRestService restService,
        ChangesFeedParameters parameters
    ) throws IOException {
        try {

            final Response response = restService.changes(
                parameters.getProfile(), null, null,
                MediaSince.asQueryParam(parameters.getMediaSince()),
                parameters.getOrder(),
                parameters.getMax(),
                parameters.getDeletes(),
                parameters.getTail(),
                parameters.getReasonFilter());
            final InputStream inputStream = toInputStream(response);

            return new JsonArrayIterator<>(
                inputStream,
                MediaChange.class,
                () -> closeQuietly(inputStream, response)
            );
        } catch (ProcessingException pi) {
            Throwable t = pi.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else if (t instanceof IOException) {
                throw (IOException) t;
            } else {
                throw new RuntimeException(t.getMessage(), t);
            }
        }

    }
    public static JsonArrayIterator changes(MediaRestService restService, String profile, Instant since, String mid, Order order, Integer max, Deletes deletes) throws IOException {
        return changes(restService, profile, since, mid, order, max, deletes, Tail.IF_EMPTY);
    }

    /**
     * @deprecated use {@code MediaSince#of(since, mid).asQueryParam()}
     */
    @Deprecated
    public static String sinceString(Instant since, String mid) {
        String sinceString = since == null ? null : since.toString();
        if (mid != null && sinceString != null) {
            sinceString += "," + mid;
        }
        return sinceString;
    }

    public static CountedIterator iterate(MediaRestService restService, MediaForm form, String profile) {
        return iterate(restService, form, profile, true);
    }

    public static CountedIterator iterate(MediaRestService restService, MediaForm form, String profile , boolean progressLogging) {
        return iterate(restService, form, profile, progressLogging, Integer.MAX_VALUE);
    }

    /**
     *
     */
    public static CountedIterator iterate(MediaRestService restService, MediaForm form, String profile , boolean progressLogging, int max) {
        return iterate(() -> restService.iterate(form, profile, null, 0L, max), progressLogging, "iterate-" + profile + "-");
    }

    public static CountedIterator iterate(Supplier response, boolean progressLogging, String filePrefix) {
        return new LazyIterator<>(() -> {
            try {
                final Response res = response.get();
                final InputStream inputStream = toInputStream(res);
                // Cache the stream to a file first.
                // If we don't do this, the stream seems to be inadvertedly truncated sometimes if the client doesn't consume the iterator fast enough.
                final FileCachingInputStream cacheToFile = FileCachingInputStream.builder()
                    .filePrefix(filePrefix)
                    .batchSize(1000000L)
                    .logger(log)
                    .progressLogging(progressLogging)
                    .input(inputStream)
                    .build();
                return JsonArrayIterator.builder()
                    .inputStream(cacheToFile)
                    .valueClass(MediaObject.class)
                    .callback(() -> closeQuietly(inputStream, cacheToFile, res))
                    .logger(log)
                    .build();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
    }


    public static void closeQuietly(AutoCloseable... closeables) {
        for (AutoCloseable c : closeables) {
            if (c != null) {
                try {
                    c.close();
                } catch (Throwable e) {
                    log.info(e.getMessage());
                }
            }
        }
    }

    /**
     * Converts response to inputstream
     */
    public static InputStream toInputStream(Response response) {
        // I would rather use some utility to map response status codes to proper exceptions, but I can't find one.
        // This seems incomplete now
        try {
            if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) {
                InputStream inputStream = response.readEntity(InputStream.class);
                return inputStream;
            } else if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
                throw new NotFoundException(response);
            } else if (response.getStatusInfo().getFamily() == Response.Status.Family.CLIENT_ERROR) {
                throw new BadRequestException(response);
            } else if (response.getStatusInfo().getFamily() == Response.Status.Family.SERVER_ERROR) {
                throw new ServerErrorException(response);
            } else {
                throw new RuntimeException(response.readEntity(String.class));
            }
        } finally {
            NpoApiClients.dealWithHeaders(slf4j(log), s -> s.equalsIgnoreCase(Headers.NPO_WARNING_HEADER) ? Level.WARN : Level.DEBUG);
        }
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy