OMDb.java

/*
 * omdb-java-client - A client to access the OMDB API
 * Copyright © 2025 Andy Miles (andy.miles@amilesend.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.amilesend.omdb.client;

import com.amilesend.client.connection.auth.NoOpAuthManager;
import com.amilesend.client.parse.parser.BasicParser;
import com.amilesend.client.util.Validate;
import com.amilesend.omdb.client.connection.OmdbConnection;
import com.amilesend.omdb.client.connection.OmdbConnectionBuilder;
import com.amilesend.omdb.client.model.QueryParameterBasedRequest;
import com.amilesend.omdb.client.model.movie.GetMovieBasedRequest;
import com.amilesend.omdb.client.model.movie.GetMovieByIdRequest;
import com.amilesend.omdb.client.model.movie.GetMovieByTitleRequest;
import com.amilesend.omdb.client.model.movie.SearchMovieRequest;
import com.amilesend.omdb.client.model.movie.type.Movie;
import com.amilesend.omdb.client.model.tv.GetEpisodeBasedRequest;
import com.amilesend.omdb.client.model.tv.GetEpisodeByIdRequest;
import com.amilesend.omdb.client.model.tv.GetEpisodeByTitleRequest;
import com.amilesend.omdb.client.model.tv.GetSeasonBasedRequest;
import com.amilesend.omdb.client.model.tv.GetSeasonByIdRequest;
import com.amilesend.omdb.client.model.tv.GetSeasonByTitleRequest;
import com.amilesend.omdb.client.model.tv.GetSeriesBasedRequest;
import com.amilesend.omdb.client.model.tv.GetSeriesByIdRequest;
import com.amilesend.omdb.client.model.tv.GetSeriesByTitleRequest;
import com.amilesend.omdb.client.model.tv.SearchSeriesRequest;
import com.amilesend.omdb.client.model.tv.type.Episode;
import com.amilesend.omdb.client.model.tv.type.Season;
import com.amilesend.omdb.client.model.tv.type.Series;
import com.amilesend.omdb.client.model.type.SearchResponse;
import com.amilesend.omdb.client.parse.GsonFactory;
import lombok.NonNull;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

/** The Open Movie Database client. */
public class OMDb {
    public static final String USER_AGENT = "OMDbJavaClient/2.0";
    public static final String API_URL = "http://www.omdbapi.com/";

    /** The underlying connection. */
    private final OmdbConnection connection;
    /** The consumer-provided API key. */
    private final String apiKey;

    /**
     * Creates a new {@code OMDb} with the provided API key.
     *
     * @param apiKey the API key
     */
    public OMDb(final String apiKey) {
        this(apiKey, USER_AGENT, null);
    }

    /**
     * Creates a nwe {@code OMDb} with the provided API key and user agent.
     *
     * @param apiKey the API key
     * @param userAgent the user agent
     */
    public OMDb(final String apiKey, final String userAgent) {
        this(apiKey, userAgent, null);
    }

    /**
     * Creates a new {@code OMDb} with the provided connection and API key.
     *
     * @param apiKey the API key
     * @param userAgent the user agent
     * @param connection the underlying connection
     */
    public OMDb(final String apiKey, final String userAgent, final OmdbConnection connection) {
        Validate.notBlank(apiKey, "apiKey must not be blank. You can obtain one via " +
                "https://www.omdbapi.com/apikey.aspx");
        Validate.notBlank(userAgent, "userAgent must not be blank");

        this.connection = Optional.ofNullable(connection).orElseGet(() -> newConnection(userAgent));
        this.apiKey = apiKey;
    }

    private static OmdbConnection newConnection(final String userAgent) {
        return new OmdbConnectionBuilder()
                .baseUrl(API_URL)
                .userAgent(userAgent)
                .gsonFactory(new GsonFactory())
                .authManager(new NoOpAuthManager())
                .httpClient(new OkHttpClient.Builder().build())
                .isGzipContentEncodingEnabled(false)
                .build();
    }

    /////////////
    // GET APIs
    /////////////

    /**
     * Gets a movie for a request by either its title or IMDB identifier.
     *
     * @param request the request
     * @return the movie
     * @see Movie
     * @see GetMovieByIdRequest
     * @see GetMovieByTitleRequest
     */
    public Movie getMovie(@NonNull final GetMovieBasedRequest request) {
        return getResource(request, Movie.class);
    }

    /**
     * Gets a TV series for a request by either its title or IMDB identifier.
     *
     * @param request the request
     * @return the series
     * @see Series
     * @see GetSeriesByIdRequest
     * @see GetSeriesByTitleRequest
     */
    public Series getSeries(@NonNull final GetSeriesBasedRequest request) {
        return getResource(request, Series.class);
    }

    /**
     * Gets a TV season for a request by either its title or IMDB identifier.
     *
     * @param request the request
     * @return the season
     * @see Season
     * @see GetSeasonByIdRequest
     * @see GetSeasonByTitleRequest
     */
    public Season getSeason(@NonNull final GetSeasonBasedRequest request) {
        return getResource(request, Season.class);
    }

    /**
     * Gets a TV episode for a request by either its series title or IMDB identifier.
     *
     * @param request the request
     * @return the episode
     * @see Episode
     * @see GetEpisodeByIdRequest
     * @see GetEpisodeByTitleRequest
     */
    public Episode getEpisode(@NonNull final GetEpisodeBasedRequest request) {
        return getResource(request, Episode.class);
    }

    ////////////////
    // Search APIs
    ////////////////

    /**
     * Searches for movies.
     *
     * @param request the request
     * @return the response containing the list of movies
     */
    public SearchResponse search(@NonNull final SearchMovieRequest request) {
        return getResource(request, SearchResponse.class);
    }

    /**
     * Searches for TV series.
     *
     * @param request the request
     * @return the response containing the list of TV series.
     */
    public SearchResponse search(@NonNull final SearchSeriesRequest request) {
        return getResource(request, SearchResponse.class);
    }

    private <T> T getResource(final QueryParameterBasedRequest request, final Class<T> returnType) {
        final HttpUrl url = request.populateQueryParameters(newUrlBuilder()).build();
        final Request httpRequest = connection.newRequestBuilder().url(url).build();

        return connection.execute(httpRequest, new BasicParser<>(returnType));
    }

    private HttpUrl.Builder newUrlBuilder() {
        return HttpUrl.parse(connection.getBaseUrl())
                .newBuilder()
                .addQueryParameter("apikey", URLEncoder.encode(String.valueOf(apiKey), StandardCharsets.UTF_8))
                .addQueryParameter("r", "json");
    }
}