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

io.castle.client.internal.backend.OkRestApiBackend Maven / Gradle / Ivy

package io.castle.client.internal.backend;

import com.google.gson.*;
import io.castle.client.Castle;
import io.castle.client.internal.config.CastleConfiguration;
import io.castle.client.internal.json.CastleGsonModel;
import io.castle.client.internal.utils.OkHttpExceptionUtil;
import io.castle.client.internal.utils.VerdictBuilder;
import io.castle.client.internal.utils.VerdictTransportModel;
import io.castle.client.model.*;
import okhttp3.*;

import java.io.IOException;

public class OkRestApiBackend implements RestApi {

    private final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    private final OkHttpClient client;
    private final CastleGsonModel model;
    private final CastleConfiguration configuration;

    private final HttpUrl track;
    private final HttpUrl authenticate;
    private final HttpUrl identify;
    private final HttpUrl reviewsBase;
    private final HttpUrl deviceBase;
    private final HttpUrl userBase;
    private final HttpUrl impersonateBase;
    private final HttpUrl privacyBase;

    public OkRestApiBackend(OkHttpClient client, CastleGsonModel model, CastleConfiguration configuration) {
        HttpUrl baseUrl = HttpUrl.parse(configuration.getApiBaseUrl());
        this.client = client;
        this.model = model;
        this.configuration = configuration;
        this.track = baseUrl.resolve("/v1/track");
        this.authenticate = baseUrl.resolve("/v1/authenticate");
        this.reviewsBase = baseUrl.resolve("/v1/reviews/");
        this.identify = baseUrl.resolve("/v1/identify");
        this.deviceBase = baseUrl.resolve("/v1/devices/");
        this.userBase = baseUrl.resolve("/v1/users/");
        this.impersonateBase = baseUrl.resolve("/v1/impersonate");
        this.privacyBase = baseUrl.resolve("/v1/privacy/");
    }

    @Override
    public void sendTrackRequest(JsonElement payload, final AsyncCallbackHandler asyncCallbackHandler) {
        RequestBody body = buildRequestBody(payload);
        Request request = new Request.Builder()
                .url(track)
                .post(body)
                .build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Castle.logger.error("HTTP layer. Error sending track request.", e);
                if (asyncCallbackHandler != null) {
                    asyncCallbackHandler.onException(e);
                }
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try (ResponseBody responseBody = response.body()) {
                    if (asyncCallbackHandler != null) {
                        asyncCallbackHandler.onResponse(response.isSuccessful());
                    }
                }
            }
        });
    }

    @Override
    public Verdict sendAuthenticateSync(JsonElement payloadJson) {
        final String userId = getUserIdFromPayload(payloadJson);

        RequestBody body = buildRequestBody(payloadJson);
        Request request = new Request.Builder()
                .url(authenticate)
                .post(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            return extractAuthenticationAction(response, userId);
        } catch (IOException e) {
            Castle.logger.error("HTTP layer. Error sending request.", e);
            if (configuration.getAuthenticateFailoverStrategy().isThrowTimeoutException()) {
                throw OkHttpExceptionUtil.handle(e);
            } else {
                return VerdictBuilder.failover(e.getMessage())
                        .withAction(configuration.getAuthenticateFailoverStrategy().getDefaultAction())
                        .withUserId(userId)
                        .build();
            }
        }
    }

    @Override
    public void sendAuthenticateAsync(JsonElement payloadJson, final AsyncCallbackHandler asyncCallbackHandler) {
        final String userId = getUserIdFromPayload(payloadJson);

        RequestBody body = buildRequestBody(payloadJson);
        Request request = new Request.Builder()
                .url(authenticate)
                .post(body)
                .build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                if (configuration.getAuthenticateFailoverStrategy().isThrowTimeoutException()) {
                    asyncCallbackHandler.onException(OkHttpExceptionUtil.handle(e));
                } else {
                    asyncCallbackHandler.onResponse(
                            VerdictBuilder.failover(e.getMessage())
                                    .withAction(configuration.getAuthenticateFailoverStrategy().getDefaultAction())
                                    .withUserId(userId)
                                    .build()
                    );
                }
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try (ResponseBody responseBody = response.body()) {
                    asyncCallbackHandler.onResponse(extractAuthenticationAction(response, userId));
                }
            }
        });
    }

    private String getUserIdFromPayload(JsonElement payloadJson) {
        final String userId = ((JsonObject) payloadJson).has("user_id") ? ((JsonObject) payloadJson).get("user_id").getAsString() : null;
        if (userId == null) {
            Castle.logger.warn("Authenticate called with user_id null. Is this correct?");
        }
        return userId;
    }

    private RequestBody buildRequestBody(JsonElement payloadJson) {
        JsonObject json = payloadJson.getAsJsonObject();
        return RequestBody.create(JSON, json.toString());
    }

    private Verdict extractAuthenticationAction(Response response, String userId) throws IOException {
        String errorReason = response.message();
        String jsonResponse = response.body().string();

        if (response.isSuccessful()) {
            Gson gson = model.getGson();
            JsonParser jsonParser = model.getJsonParser();
            VerdictTransportModel transport = gson.fromJson(jsonResponse, VerdictTransportModel.class);
            if (transport != null && transport.getAction() != null && transport.getUserId() != null) {
                return VerdictBuilder.fromTransport(transport, jsonParser.parse(jsonResponse));
            } else {
                errorReason = "Invalid JSON in response";
            }
        }

        if (response.code() >= 500) {
            //Use failover for error backends calls.
            if (!configuration.getAuthenticateFailoverStrategy().isThrowTimeoutException()) {
                Verdict verdict = VerdictBuilder.failover(errorReason)
                        .withAction(configuration.getAuthenticateFailoverStrategy().getDefaultAction())
                        .withUserId(userId)
                        .build();
                return verdict;
            } else {
                throw new CastleApiInternalServerErrorException(response);
            }
        }

        // Could not extract Verdict, so fail for client logic space.
        throw new CastleRuntimeException(response);
    }

    @Override
    public void sendIdentifyRequest(String userId, JsonObject contextJson, boolean active, JsonElement traitsJson) {
        JsonObject json = new JsonObject();
        json.add("user_id", new JsonPrimitive(userId));
//        json.add("active", new JsonPrimitive(active));
        contextJson.add("active", new JsonPrimitive(active));
        json.add("context", contextJson);
        if (traitsJson != null) {
            json.add("traits", traitsJson);
        }
        RequestBody body = RequestBody.create(JSON, json.toString());
        Request request = new Request.Builder()
                .url(identify)
                .post(body)
                .build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Castle.logger.error("HTTP layer. Error sending request.", e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                response.close();

                Castle.logger.debug("Identify request successful");
            }
        });
    }

    @Override
    public Review sendReviewRequestSync(String reviewId) {
        Request request = createReviewRequest(reviewId);
        try (Response response = client.newCall(request).execute()) {
            return extractReview(response);
        } catch (IOException e) {
            throw OkHttpExceptionUtil.handle(e);
        }
    }

    @Override
    public void sendReviewRequestAsync(String reviewId, final AsyncCallbackHandler callbackHandler) {
        Request request = createReviewRequest(reviewId);
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callbackHandler.onException(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try (ResponseBody responseBody = response.body()) {
                    callbackHandler.onResponse(extractReview(response));
                }
            }
        });
    }

    @Override
    public Boolean sendPrivacyRemoveUser(String userId) {
        Request request = createPrivacyRemoveRequest(userId);
        try (Response response = client.newCall(request).execute()) {
            return extractResponse(response);
        } catch (IOException e) {
            throw OkHttpExceptionUtil.handle(e);
        }
    }

    @Override
    public CastleUserDevice sendApproveDeviceRequestSync(String deviceToken) {
        Request request = createApproveDeviceRequest(deviceToken);
        try (Response response = client.newCall(request).execute()) {
            return extractDevice(response);
        } catch (IOException e) {
            throw OkHttpExceptionUtil.handle(e);
        }
    }

    @Override
    public CastleUserDevice sendReportDeviceRequestSync(String deviceToken) {
        Request request = createReportDeviceRequest(deviceToken);
        try (Response response = client.newCall(request).execute()) {
            return extractDevice(response);
        } catch (IOException e) {
            throw OkHttpExceptionUtil.handle(e);
        }
    }

    @Override
    public CastleUserDevices sendGetUserDevicesRequestSync(String userId) {
        Request request = createGetUserDevicesRequest(userId);
        try (Response response = client.newCall(request).execute()) {
            return extractDevices(response);
        } catch (IOException e) {
            throw OkHttpExceptionUtil.handle(e);
        }
    }

    @Override
    public CastleUser sendArchiveUserDevicesRequestSync(String userId) {
        Request request = createArchiveUserDevicesRequest(userId);
        try (Response response = client.newCall(request).execute()) {
            return extractUser(response);
        } catch (IOException e) {
            throw OkHttpExceptionUtil.handle(e);
        }
    }

    @Override
    public CastleUserDevice sendGetUserDeviceRequestSync(String deviceToken) {
        Request request = createGetUserDeviceRequest(deviceToken);
        try (Response response = client.newCall(request).execute()) {
            return extractDevice(response);
        } catch (IOException e) {
            throw OkHttpExceptionUtil.handle(e);
        }
    }

    @Override
    public CastleSuccess sendImpersonateStartRequestSync(String userId, String impersonator, JsonObject contextJson) {
        Request request = createImpersonateStartRequest(userId, impersonator, contextJson);
        try (Response response = client.newCall(request).execute()) {
            return extractSuccess(response);
        } catch (IOException e) {
            throw OkHttpExceptionUtil.handle(e);
        }
    }

    @Override
    public CastleSuccess sendImpersonateEndRequestSync(String userId, String impersonator, JsonObject contextJson) {
        Request request = createImpersonateEndRequest(userId, impersonator, contextJson);
        try (Response response = client.newCall(request).execute()) {
            return extractSuccess(response);
        } catch (IOException e) {
            throw OkHttpExceptionUtil.handle(e);
        }
    }

    private Request createReviewRequest(String reviewId) {
        HttpUrl reviewUrl = reviewsBase.resolve(reviewId);
        return new Request.Builder()
                .url(reviewUrl)
                .get()
                .build();
    }

    private Review extractReview(Response response) throws IOException {
        return (Review) extract(response, Review.class);
    }

    private CastleUserDevice extractDevice(Response response) throws IOException {
        return (CastleUserDevice) extract(response, CastleUserDevice.class);
    }

    private CastleUserDevices extractDevices(Response response) throws IOException {
        return (CastleUserDevices) extract(response, CastleUserDevices.class);
    }

    private Object extract(Response response, Class clazz) throws IOException {
        if (response.isSuccessful()) {
            String jsonResponse = response.body().string();
            Gson gson = model.getGson();
            return gson.fromJson(jsonResponse, clazz);
        } else if (response.code() == 404) {
            return null;
        }
        OkHttpExceptionUtil.handle(response);
        return null;
    }

    private CastleSuccess extractSuccess(Response response) throws IOException {
        if (response.isSuccessful()) {
            if (response.body() != null) {
                String jsonResponse = response.body().string();
                Gson gson = model.getGson();
                return gson.fromJson(jsonResponse, CastleSuccess.class);
            }
        }
        OkHttpExceptionUtil.handle(response);
        return null;
    }

    private Boolean extractResponse(Response response) {
        if (response.isSuccessful()) {
            return true;
        } else if (response.code() == 404) {
            return null;
        }
        OkHttpExceptionUtil.handle(response);
        return false;
    }

    private CastleUser extractUser(Response response) throws IOException {
        return (CastleUser) extract(response, CastleUser.class);
    }

    private Request createApproveDeviceRequest(String deviceToken) {
        HttpUrl approveDeviceUrl = deviceBase.resolve(deviceToken + "/approve");
        return new Request.Builder()
                .url(approveDeviceUrl)
                .put(createEmptyRequestBody())
                .build();
    }

    private Request createReportDeviceRequest(String deviceToken) {
        HttpUrl reportDeviceUrl = deviceBase.resolve(deviceToken + "/report");
        return new Request.Builder()
                .url(reportDeviceUrl)
                .put(createEmptyRequestBody())
                .build();
    }

    private Request createGetUserDevicesRequest(String userId) {
        HttpUrl getUserDevicesUrl = userBase.resolve(userId + "/devices");
        return new Request.Builder()
                .url(getUserDevicesUrl)
                .get()
                .build();
    }

    private Request createGetUserDeviceRequest(String deviceToken) {
        HttpUrl getUserDeviceUrl = deviceBase.resolve(deviceToken);
        return new Request.Builder()
                .url(getUserDeviceUrl)
                .get()
                .build();
    }

    private Request createArchiveUserDevicesRequest(String userId) {
        HttpUrl archiveUserDevicesUrl = userBase.resolve(userId + "/archive_devices");
        return new Request.Builder()
                .url(archiveUserDevicesUrl)
                .put(createEmptyRequestBody())
                .build();
    }

    private Request createImpersonateStartRequest(String userId, String impersonator, JsonObject contextJson) {
        HttpUrl impersonateUrl = impersonateBase;

        ImpersonatePayload payload = new ImpersonatePayload(userId, impersonator, contextJson);

        RequestBody body = RequestBody.create(JSON, model.getGson().toJson(payload));

        return new Request.Builder()
                .url(impersonateUrl)
                .post(body)
                .build();
    }

    private Request createImpersonateEndRequest(String userId, String impersonator, JsonObject contextJson) {
        HttpUrl impersonateUrl = impersonateBase;

        ImpersonatePayload payload = new ImpersonatePayload(userId, impersonator, contextJson);

        RequestBody body = RequestBody.create(JSON, model.getGson().toJson(payload));

        return new Request.Builder()
                .url(impersonateUrl)
                .delete(body)
                .build();
    }

    private Request createPrivacyRemoveRequest(String userId) {
        HttpUrl privacyRemoveUrl = privacyBase.resolve("users/" + userId);
        return new Request.Builder()
                .url(privacyRemoveUrl)
                .delete()
                .build();
    }

    private RequestBody createEmptyRequestBody() {
        return RequestBody.create(null, new byte[0]);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy