io.gameup.android.GameUpSession.java Source code

Java tutorial

Introduction

Here is the source code for io.gameup.android.GameUpSession.java

Source

/*
 * Copyright 2014-2015 GameUp
 *
 * 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.gameup.android;

import android.net.Uri;

import com.google.gson.JsonParseException;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.IOException;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.util.List;

import io.gameup.android.entity.Achievement;
import io.gameup.android.entity.Gamer;
import io.gameup.android.entity.LeaderboardAndRank;
import io.gameup.android.entity.Rank;
import io.gameup.android.http.OkHttpClientFactory;
import io.gameup.android.http.RequestFactory;
import io.gameup.android.entity.AchievementList;
import io.gameup.android.json.AchievementProgress;
import io.gameup.android.json.GsonFactory;
import io.gameup.android.json.LeaderboardSubmission;
import io.gameup.android.json.StorageGetWrapper;
import lombok.Getter;
import lombok.NonNull;

/**
 * Represents a session for an authenticated user.
 *
 * All operations are thread-safe.
 *
 * Since this represents a particular user, the token is fixed - if it needs to
 * change then a new session should be created via the GameUp.login() method.
 *
 * Call the ping() method to check that the remote service is reachable, and
 * that the session is accepted with the given API key.
 */
public class GameUpSession {

    /** The user identification token for this session. */
    private final String token;

    /** Rough local device timestamp when this session was created. */
    @Getter
    private final long createdAt;

    /**
     * Initialise with the given token.
     *
     * @param token The token key to use, must not be null.
     */
    GameUpSession(final @NonNull String token) {
        this.token = token;
        this.createdAt = System.currentTimeMillis();
    }

    /**
     * Serialise this instance to a String, safe for storage or transmission.
     * The resulting String is ~230 bytes long.
     *
     * @return A String representation of this instance.
     */
    @Override
    public String toString() {
        return GsonFactory.get().toJson(this);
    }

    /**
     * Load a session from the given String. Will fail hard if the input does
     * not represent a GameUpSession instance.
     *
     * @param gameUpSession The String to attempt to load from.
     * @return The retrieved GameUpSession instance.
     * @throws IOException if the given string cannot be read as a compatible
     *         GameUpSession instance.
     */
    public static GameUpSession fromString(final @NonNull String gameUpSession) throws IOException {
        try {
            return GsonFactory.get().fromJson(gameUpSession, GameUpSession.class);
        } catch (final JsonParseException e) {
            throw new IOException("Input is not a valid GameUpSession");
        }
    }

    /**
     * Ping the GameUp service to check it is reachable and ready to handle
     * requests.
     *
     * @param apiKey The API key to use.
     * @return true if the service is reachable, responds correctly, and accepts
     *         the API key and token combination; false otherwise.
     * @throws IOException when a network or communication error occurs.
     */
    public boolean ping(final @NonNull String apiKey) throws IOException {
        return GameUp.ping(apiKey, token);
    }

    /**
     * Get information about the gamer who owns this session.
     *
     * @param apiKey The API key to use.
     * @return An entity containing gamer information.
     * @throws IOException when a network or communication error occurs.
     */
    public Gamer gamer(final @NonNull String apiKey) throws IOException {
        Reader in = null;
        try {
            final Request request = RequestFactory.get(new Uri.Builder().scheme(GameUp.SCHEME)
                    .encodedAuthority(GameUp.API_SERVER).appendPath("v0").appendPath("gamer").build(), apiKey,
                    token);
            final Response response = OkHttpClientFactory.getClient().newCall(request).execute();

            if (response.code() != HttpURLConnection.HTTP_OK) {
                throw new IOException("Operation returned HTTP " + response.code());
            }
            in = response.body().charStream();

            try {
                return GsonFactory.get().fromJson(in, Gamer.class);
            } catch (final JsonParseException e) {
                throw new IOException("Response data does not match expected entity");
            }
        } finally {
            Utils.closeQuietly(in);
        }
    }

    /**
     * Perform a key-value storage write operation, storing data as JSON. Data
     * is private per-user and per-game.
     *
     * NOTE: This is not designed to store confidential data, such as payment
     * information etc.
     *
     * @param apiKey The API key to use.
     * @param key The key to store the given data under.
     * @param value The object to serialise and store.
     * @throws IOException when a network or communication error occurs.
     */
    public void storagePut(final @NonNull String apiKey, final @NonNull String key, final @NonNull Object value)
            throws IOException {
        final Request request = RequestFactory.put(
                new Uri.Builder().scheme(GameUp.SCHEME).encodedAuthority(GameUp.API_SERVER).appendPath("v0")
                        .appendPath("gamer").appendPath("storage").appendPath(key).build(),
                apiKey, token, GsonFactory.get().toJson(value));
        final Response response = OkHttpClientFactory.getClient().newCall(request).execute();

        if (response.code() != HttpURLConnection.HTTP_NO_CONTENT) {
            throw new IOException("Operation returned HTTP " + response.code());
        }
    }

    /**
     * Perform a key-value storage read operation.
     *
     * @param apiKey The API key to use.
     * @param key The key to attempt to read data from.
     * @param type The class literal to attempt to deserialise as.
     * @return The entity requested, or null if there was no data.
     * @throws IOException when a network or communication error occurs.
     */
    public <T> T storageGet(final @NonNull String apiKey, final @NonNull String key, final @NonNull Class<T> type)
            throws IOException {
        Reader in = null;
        try {
            final Request request = RequestFactory.get(
                    new Uri.Builder().scheme(GameUp.SCHEME).encodedAuthority(GameUp.API_SERVER).appendPath("v0")
                            .appendPath("gamer").appendPath("storage").appendPath(key).build(),
                    apiKey, token);
            final Response response = OkHttpClientFactory.getClient().newCall(request).execute();

            switch (response.code()) {
            case HttpURLConnection.HTTP_OK:
                in = response.body().charStream();

                try {
                    final StorageGetWrapper responseWrapper = GsonFactory.get().fromJson(in,
                            StorageGetWrapper.class);

                    return GsonFactory.get().fromJson(responseWrapper.getValue(), type);
                } catch (final JsonParseException e) {
                    throw new IOException("Response data does not match expected entity");
                }
            case HttpURLConnection.HTTP_NOT_FOUND:
                return null;
            default:
                throw new IOException("Operation returned HTTP " + response.code());
            }
        } finally {
            Utils.closeQuietly(in);
        }
    }

    /**
     * Perform a key-value storage delete operation. Will silently ignore absent
     * data.
     *
     * @param apiKey The API key to use.
     * @param key The key to delete data from.
     * @throws IOException when a network or communication error occurs.
     */
    public void storageDelete(final @NonNull String apiKey, final @NonNull String key) throws IOException {
        final Request request = RequestFactory
                .delete(new Uri.Builder().scheme(GameUp.SCHEME).encodedAuthority(GameUp.API_SERVER).appendPath("v0")
                        .appendPath("gamer").appendPath("storage").appendPath(key).build(), apiKey, token);
        final Response response = OkHttpClientFactory.getClient().newCall(request).execute();

        if (response.code() != HttpURLConnection.HTTP_NO_CONTENT) {
            throw new IOException("Operation returned HTTP " + response.code());
        }
    }

    /**
     * Report progress towards a given achievement. Equivalent to calling
     * achievement(apiKey, achievementId, 1) below.
     *
     * Progress will be "1". This method is intended for convenience when
     * triggering "normal"-type achievements, but will still add 1 to an
     * "incremental"-type achievement if needed.
     *
     * @param apiKey The API key to use.
     * @param achievementId The internal Achievement ID to interact with.
     * @return An Achievement instance if this call results in an achievement
     *         being completed or progress is reported, null otherwise.
     * @throws IOException when a network or communication error occurs.
     */
    public Achievement achievement(final @NonNull String apiKey, final @NonNull String achievementId)
            throws IOException {
        return achievement(apiKey, achievementId, 1);
    }

    /**
     * Report progress towards a given achievement.
     *
     * @param apiKey The API key to use.
     * @param achievementId The internal Achievement ID to interact with.
     * @param count The progress amount to report.
     * @return An Achievement instance if this call results in an achievement
     *         being completed or progress is reported, null otherwise.
     * @throws IOException when a network or communication error occurs.
     */
    public Achievement achievement(final @NonNull String apiKey, final @NonNull String achievementId,
            final int count) throws IOException {
        final Request request = RequestFactory.post(
                new Uri.Builder().scheme(GameUp.SCHEME).encodedAuthority(GameUp.API_SERVER).appendPath("v0")
                        .appendPath("gamer").appendPath("achievement").appendPath(achievementId).build(),
                apiKey, token, GsonFactory.get().toJson(new AchievementProgress(count)));
        final Response response = OkHttpClientFactory.getClient().newCall(request).execute();

        Reader in = null;
        try {
            switch (response.code()) {
            case HttpURLConnection.HTTP_OK:
                in = response.body().charStream();

                try {
                    return GsonFactory.get().fromJson(in, Achievement.class);
                } catch (final JsonParseException e) {
                    throw new IOException("Response data does not match expected entity");
                }
            case HttpURLConnection.HTTP_NO_CONTENT:
                return null;
            default:
                throw new IOException("Operation returned HTTP " + response.code());
            }
        } finally {
            Utils.closeQuietly(in);
        }
    }

    /**
     * Get a list of achievements available for the game, including any gamer
     * data such as progress or completed timestamps.
     *
     * @param apiKey The API key to use.
     * @return A List containing Achievement instances, may be empty if none are
     *         returned for the current game.
     * @throws IOException when a network or communication error occurs.
     */
    public AchievementList achievement(final @NonNull String apiKey) throws IOException {
        Reader in = null;
        try {
            final Request request = RequestFactory
                    .get(new Uri.Builder().scheme(GameUp.SCHEME).encodedAuthority(GameUp.API_SERVER)
                            .appendPath("v0").appendPath("gamer").appendPath("achievement").build(), apiKey, token);
            final Response response = OkHttpClientFactory.getClient().newCall(request).execute();

            if (response.code() != HttpURLConnection.HTTP_OK) {
                throw new IOException("Operation returned HTTP " + response.code());
            }
            in = response.body().charStream();

            try {
                return GsonFactory.get().fromJson(in, AchievementList.class);
            } catch (final JsonParseException e) {
                throw new IOException("Response data does not match expected entity");
            }
        } finally {
            Utils.closeQuietly(in);
        }
    }

    /**
     * Submit a new score to the specified leaderboard. The new score will only
     * overwrite any previously submitted value if it's "better" according to
     * the sorting rules of the leaderboard, but updated ranking details are
     * returned in all cases.
     *
     * @param apiKey The API key to use.
     * @param leaderboardId The private ID of the leaderboard to submit to.
     * @param score The score to submit.
     * @return A Rank instance containing updated detailed rank data for the
     *         current gamer.
     * @throws IOException when a network or communication error occurs.
     */
    public Rank leaderboard(final @NonNull String apiKey, final @NonNull String leaderboardId, final long score)
            throws IOException {
        final Request request = RequestFactory.post(
                new Uri.Builder().scheme(GameUp.SCHEME).encodedAuthority(GameUp.API_SERVER).appendPath("v0")
                        .appendPath("gamer").appendPath("leaderboard").appendPath(leaderboardId).build(),
                apiKey, token, GsonFactory.get().toJson(new LeaderboardSubmission(score)));
        final Response response = OkHttpClientFactory.getClient().newCall(request).execute();

        Reader in = null;
        try {
            switch (response.code()) {
            case HttpURLConnection.HTTP_OK:
                in = response.body().charStream();

                try {
                    return GsonFactory.get().fromJson(in, Rank.class);
                } catch (final JsonParseException e) {
                    throw new IOException("Response data does not match expected entity");
                }
            default:
                throw new IOException("Operation returned HTTP " + response.code());
            }
        } finally {
            Utils.closeQuietly(in);
        }
    }

    /**
     * Request leaderboard metadata, the current top ranked gamers, and the
     * current gamer's detailed ranking on a specified leaderboard.
     *
     * @param apiKey The API key to use.
     * @param leaderboardId The private ID of the leaderboard to request.
     * @return A corresponding LeaderboardAndRank instance.
     * @throws IOException when a network or communication error occurs.
     */
    public LeaderboardAndRank leaderboard(final @NonNull String apiKey, final @NonNull String leaderboardId)
            throws IOException {
        Reader in = null;
        try {
            final Request request = RequestFactory.get(
                    new Uri.Builder().scheme(GameUp.SCHEME).encodedAuthority(GameUp.API_SERVER).appendPath("v0")
                            .appendPath("gamer").appendPath("leaderboard").appendPath(leaderboardId).build(),
                    apiKey, token);
            final Response response = OkHttpClientFactory.getClient().newCall(request).execute();

            if (response.code() != HttpURLConnection.HTTP_OK) {
                throw new IOException("Operation returned HTTP " + response.code());
            }
            in = response.body().charStream();

            try {
                return GsonFactory.get().fromJson(in, LeaderboardAndRank.class);
            } catch (final JsonParseException e) {
                throw new IOException("Response data does not match expected entity");
            }
        } finally {
            Utils.closeQuietly(in);
        }
    }

}