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

com.jakewharton.trakt.TraktApiService Maven / Gradle / Ivy

package com.jakewharton.trakt;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.reflect.TypeToken;
import com.jakewharton.apibuilder.ApiException;
import com.jakewharton.apibuilder.ApiService;
import com.jakewharton.trakt.entities.ActivityItem;
import com.jakewharton.trakt.entities.ActivityItemBase;
import com.jakewharton.trakt.entities.TvShowEpisode;
import com.jakewharton.trakt.entities.TvShowSeason;
import com.jakewharton.trakt.enumerations.ActivityAction;
import com.jakewharton.trakt.enumerations.ActivityType;
import com.jakewharton.trakt.enumerations.DayOfTheWeek;
import com.jakewharton.trakt.enumerations.Gender;
import com.jakewharton.trakt.enumerations.ListItemType;
import com.jakewharton.trakt.enumerations.ListPrivacy;
import com.jakewharton.trakt.enumerations.MediaType;
import com.jakewharton.trakt.enumerations.Rating;
import com.jakewharton.trakt.enumerations.RatingType;
import com.jakewharton.trakt.util.Base64;

/**
 * Trakt-specific API service extension which facilitates provides helper
 * methods for performing remote method calls as well as deserializing the
 * corresponding JSON responses.
 *
 * @author Jake Wharton 
 */
public abstract class TraktApiService extends ApiService {
    /** Default connection timeout (in milliseconds). */
    private static final int DEFAULT_TIMEOUT_CONNECT = 60 * (int)TraktApiBuilder.MILLISECONDS_IN_SECOND;

    /** Default read timeout (in milliseconds). */
    private static final int DEFAULT_TIMEOUT_READ = 60 * (int)TraktApiBuilder.MILLISECONDS_IN_SECOND;

    /** HTTP header name for authorization. */
    private static final String HEADER_AUTHORIZATION = "Authorization";

    /** HTTP authorization type. */
    private static final String HEADER_AUTHORIZATION_TYPE = "Basic";

    /** Character set used for encoding and decoding transmitted values. */
    private static final Charset UTF_8_CHAR_SET = Charset.forName(ApiService.CONTENT_ENCODING);

    /** HTTP post method name. */
    private static final String HTTP_METHOD_POST = "POST";

    /** Format for decoding JSON dates in string format. */
    private static final SimpleDateFormat JSON_STRING_DATE = new SimpleDateFormat("yyy-MM-dd");

    /** Default plugin version debug string. */
    private static final String DEFAULT_PLUGIN_VERSION = Info.FULL_NAME;

    /** Default media center version debug string. */
    private static final String DEFAULT_MEDIA_CENTER_VERSION = Info.FULL_NAME;

    /** Default media center build date debug string. */
    private static final String DEFAULT_MEDIA_CENTER_DATE = Info.DATE;

    /** Default application name debug string. */
    private static final String DEFAULT_APP_DATE = Info.DATE;

    /** Default application version debug string. */
    private static final String DEFAULT_APP_VERSION = Info.FULL_NAME;

    /** Time zone for Trakt dates. */
    private static final TimeZone TRAKT_TIME_ZONE = TimeZone.getTimeZone("GMT-8:00");


    /** JSON parser for reading the content stream. */
    private final JsonParser parser;

    /** API key. */
    private String apiKey;

    /** Plugin version debug string. */
    private String pluginVersion;

    /** Media center version debug string. */
    private String mediaCenterVersion;

    /** Media center build date debug string. */
    private String mediaCenterDate;

    /** Application date debug string. */
    private String appDate;

    /** Application version debug string. */
    private String appVersion;

    /** Whether or not to use SSL API endpoint. */
    private boolean useSsl;


    /**
     * Create a new Trakt service with our proper default values.
     */
    public TraktApiService() {
        this.parser = new JsonParser();

        //Setup timeout defaults
        this.setConnectTimeout(DEFAULT_TIMEOUT_CONNECT);
        this.setReadTimeout(DEFAULT_TIMEOUT_READ);

        //Setup debug string defaults
        this.setPluginVersion(DEFAULT_PLUGIN_VERSION);
        this.setMediaCenterVersion(DEFAULT_MEDIA_CENTER_VERSION);
        this.setMediaCenterDate(DEFAULT_MEDIA_CENTER_DATE);
        this.setAppDate(DEFAULT_APP_DATE);
        this.setAppVersion(DEFAULT_APP_VERSION);
    }


    /**
     * Execute request using HTTP GET.
     *
     * @param url URL to request.
     * @return JSON object.
     */
    public JsonElement get(String url) {
        return this.unmarshall(this.executeGet(url));
    }

    /**
     * Execute request using HTTP POST.
     *
     * @param url URL to request.
     * @param postBody String to use as the POST body.
     * @return JSON object.
     */
    public JsonElement post(String url, String postBody) {
        return this.unmarshall(this.executeMethod(url, postBody, null, HTTP_METHOD_POST, HttpURLConnection.HTTP_OK));
    }

    /**
     * Set email and password to use for HTTP basic authentication.
     *
     * @param username Username.
     * @param password_sha Password SHA1.
     */
    public void setAuthentication(String username, String password_sha) {
        if ((username == null) || (username.length() == 0)) {
            throw new IllegalArgumentException("Username must not be empty.");
        }
        if ((password_sha == null) || (password_sha.length() == 0)) {
            throw new IllegalArgumentException("Password SHA must not be empty.");
        }

        String source = username + ":" + password_sha;
        String authentication = HEADER_AUTHORIZATION_TYPE + " " + Base64.encodeBytes(source.getBytes());

        this.addRequestHeader(HEADER_AUTHORIZATION, authentication);
    }

    /**
     * Get the API key.
     *
     * @return Value
     */
    /*package*/ String getApiKey() {
        return this.apiKey;
    }

    /**
     * Set API key to use for client authentication by Trakt.
     *
     * @param value Value.
     */
    public void setApiKey(String value) {
        this.apiKey = value;
    }

    /**
     * Get the plugin version debug string used for scrobbling.
     *
     * @return Value.
     */
    /*package*/ String getPluginVersion() {
        return pluginVersion;
    }

    /**
     * Set the plugin version debug string used for scrobbling.
     *
     * @param pluginVersion Value.
     */
    public void setPluginVersion(String pluginVersion) {
        this.pluginVersion = pluginVersion;
    }

    /**
     * Get the media center version debug string used for scrobbling.
     *
     * @return Value.
     */
    /*package*/ String getMediaCenterVersion() {
        return mediaCenterVersion;
    }

    /**
     * Set the media center version debug string used for scrobbling.
     *
     * @param mediaCenterVersion Value.
     */
    public void setMediaCenterVersion(String mediaCenterVersion) {
        this.mediaCenterVersion = mediaCenterVersion;
    }

    /**
     * Get the media center build date debug string used for scrobbling.
     *
     * @return Value.
     */
    /*package*/ String getMediaCenterDate() {
        return mediaCenterDate;
    }

    /**
     * Set the media center build date debug string used for scrobbling.
     *
     * @param mediaCenterDate Value.
     */
    public void setMediaCenterDate(String mediaCenterDate) {
        this.mediaCenterDate = mediaCenterDate;
    }

    /**
     * Get the application date debug string used for checking in.
     *
     * @return Value.
     */
    /*package*/ String getAppDate() {
        return appDate;
    }

    /**
     * Set the application date debug string used for checking in.
     *
     * @param appDate Value.
     */
    public void setAppDate(String appDate) {
        this.appDate = appDate;
    }

    /**
     * Get the application version debug string used for checking in.
     *
     * @return Value.
     */
    /*package*/ String getAppVersion() {
        return appVersion;
    }

    /**
     * Set the application version debug string used for checking in.
     *
     * @param appVersion Value.
     */
    public void setAppVersion(String appVersion) {
        this.appVersion = appVersion;
    }

    /**
     * Get whether or not we want to use the SSL API endpoint.
     *
     * @return Value.
     */
    /*package*/ boolean getUseSsl() {
        return useSsl;
    }

    /**
     * Set whether or not to use the SSL API endpoint.
     *
     * @param useSsl Value.
     */
    public void setUseSsl(boolean useSsl) {
        this.useSsl = useSsl;
    }

    /**
     * Use GSON to deserialize a JSON object to a native class representation.
     *
     * @param  Native class type.
     * @param typeToken Native class type wrapper.
     * @param response Serialized JSON object.
     * @return Deserialized native instance.
     */
    @SuppressWarnings("unchecked")
    protected  T unmarshall(TypeToken typeToken, JsonElement response) {
        return (T)TraktApiService.getGsonBuilder().create().fromJson(response, typeToken.getType());
    }

    /**
     * Use GSON to deserialize a JSON string to a native class representation.
     *
     * @param  Native class type.
     * @param typeToken Native class type wrapper.
     * @param reponse Serialized JSON string.
     * @return Deserialized native instance.
     */
    @SuppressWarnings("unchecked")
    protected  T unmarshall(TypeToken typeToken, String reponse) {
        return (T)TraktApiService.getGsonBuilder().create().fromJson(reponse, typeToken.getType());
    }

    /**
     * Read the entirety of an input stream and parse to a JSON object.
     *
     * @param jsonContent JSON content input stream.
     * @return Parsed JSON object.
     */
    protected JsonElement unmarshall(InputStream jsonContent) {
        try {
            JsonElement element = this.parser.parse(new InputStreamReader(jsonContent, UTF_8_CHAR_SET));
            if (element.isJsonObject()) {
                return element.getAsJsonObject();
            } else if (element.isJsonArray()) {
                return element.getAsJsonArray();
            } else {
                throw new ApiException("Unknown content found in response." + element);
            }
        } catch (Exception e) {
            throw new ApiException(e);
        } finally {
            ApiService.closeStream(jsonContent);
        }
    }

    /**
     * Create a {@link GsonBuilder} and register all of the custom types needed
     * in order to properly deserialize complex Trakt-specific type.
     *
     * @return Assembled GSON builder instance.
     */
    public static GsonBuilder getGsonBuilder() {
        GsonBuilder builder = new GsonBuilder();

        //class types
        builder.registerTypeAdapter(Integer.class, new JsonDeserializer() {
            @Override
            public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                try {
                    return new Integer(json.getAsInt());
                } catch (NumberFormatException e) {
                    return null;
                }
            }
        });
        builder.registerTypeAdapter(Date.class, new JsonDeserializer() {
            @Override
            public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                try {
                    long value = json.getAsLong();
                    Calendar date = Calendar.getInstance(TRAKT_TIME_ZONE);
                    date.setTimeInMillis(value * TraktApiBuilder.MILLISECONDS_IN_SECOND);
                    return date.getTime();
                } catch (NumberFormatException outer) {
                    try {
                        return JSON_STRING_DATE.parse(json.getAsString());
                    } catch (ParseException inner) {
                        throw new JsonParseException(outer);
                    }
                }
            }
        });
        builder.registerTypeAdapter(Calendar.class, new JsonDeserializer() {
            @Override
            public Calendar deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                Calendar value = Calendar.getInstance(TRAKT_TIME_ZONE);
                value.setTimeInMillis(json.getAsLong() * TraktApiBuilder.MILLISECONDS_IN_SECOND);
                return value;
            }
        });
        builder.registerTypeAdapter(TvShowSeason.Episodes.class, new JsonDeserializer() {
            @Override
            public TvShowSeason.Episodes deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                TvShowSeason.Episodes episodes = new TvShowSeason.Episodes();
                try {
                    if (json.isJsonArray()) {
                        if (json.getAsJsonArray().get(0).isJsonPrimitive()) {
                            //Episode number list
                            Field fieldNumbers = TvShowSeason.Episodes.class.getDeclaredField("numbers");
                            fieldNumbers.setAccessible(true);
                            fieldNumbers.set(episodes, context.deserialize(json, (new TypeToken>() {}).getType()));
                        } else {
                            //Episode object list
                            Field fieldList = TvShowSeason.Episodes.class.getDeclaredField("episodes");
                            fieldList.setAccessible(true);
                            fieldList.set(episodes, context.deserialize(json, (new TypeToken>() {}).getType()));
                        }
                    } else {
                        //Episode count
                        Field fieldCount = TvShowSeason.Episodes.class.getDeclaredField("count");
                        fieldCount.setAccessible(true);
                        fieldCount.set(episodes, new Integer(json.getAsInt()));
                    }
                } catch (SecurityException e) {
                    throw new JsonParseException(e);
                } catch (NoSuchFieldException e) {
                    throw new JsonParseException(e);
                } catch (IllegalArgumentException e) {
                    throw new JsonParseException(e);
                } catch (IllegalAccessException e) {
                    throw new JsonParseException(e);
                }
                return episodes;
            }
        });
        builder.registerTypeAdapter(ActivityItemBase.class, new JsonDeserializer() {
            //XXX See: https://groups.google.com/d/topic/traktapi/GQlT9HfAEjw/discussion
            @Override
            public ActivityItemBase deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                if (json.isJsonArray()) {
                    if (json.getAsJsonArray().size() != 0) {
                        throw new JsonParseException("\"watched\" field returned a non-empty array.");
                    }
                    return null;
                } else {
                    return context.deserialize(json, ActivityItem.class);
                }
            }
        });
        //enum types
        builder.registerTypeAdapter(ActivityAction.class, new JsonDeserializer() {
            @Override
            public ActivityAction deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                return ActivityAction.fromValue(json.getAsString());
            }
        });
        builder.registerTypeAdapter(ActivityType.class, new JsonDeserializer() {
            @Override
            public ActivityType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                return ActivityType.fromValue(json.getAsString());
            }
        });
        builder.registerTypeAdapter(DayOfTheWeek.class, new JsonDeserializer() {
            @Override
            public DayOfTheWeek deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {
                return DayOfTheWeek.fromValue(arg0.getAsString());
            }
        });
        builder.registerTypeAdapter(Gender.class, new JsonDeserializer() {
            @Override
            public Gender deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {
                return Gender.fromValue(arg0.getAsString());
            }
        });
        builder.registerTypeAdapter(ListItemType.class, new JsonDeserializer() {
            @Override
            public ListItemType deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {
                return ListItemType.fromValue(arg0.getAsString());
            }
        });
        builder.registerTypeAdapter(ListPrivacy.class, new JsonDeserializer() {
            @Override
            public ListPrivacy deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {
                return ListPrivacy.fromValue(arg0.getAsString());
            }
        });
        builder.registerTypeAdapter(MediaType.class, new JsonDeserializer() {
            @Override
            public MediaType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                return MediaType.fromValue(json.getAsString());
            }
        });
        builder.registerTypeAdapter(Rating.class, new JsonDeserializer() {
            @Override
            public Rating deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                return Rating.fromValue(json.getAsString());
            }
        });
        builder.registerTypeAdapter(Rating.class, new JsonSerializer() {
            @Override
            public JsonElement serialize(Rating src, Type typeOfSrc, JsonSerializationContext context) {
                return new JsonPrimitive(src.toString());
            }
        });
        builder.registerTypeAdapter(RatingType.class, new JsonDeserializer() {
            @Override
            public RatingType deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {
                return RatingType.fromValue(arg0.getAsString());
            }
        });

        return builder;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy