com.tomtom.speedtools.services.push.implementation.gcm.GCMConnector.java Source code

Java tutorial

Introduction

Here is the source code for com.tomtom.speedtools.services.push.implementation.gcm.GCMConnector.java

Source

/*
 * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com).
 *
 * 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 com.tomtom.speedtools.services.push.implementation.gcm;

import com.google.android.gcm.server.Constants;
import com.google.android.gcm.server.Message;
import com.google.android.gcm.server.Message.Builder;
import com.google.android.gcm.server.Result;
import com.google.android.gcm.server.Sender;
import com.tomtom.speedtools.json.JsonRenderable;
import com.tomtom.speedtools.objects.Immutables;
import com.tomtom.speedtools.services.push.InvalidPushTokenException;
import com.tomtom.speedtools.services.push.PushConnectionException;
import com.tomtom.speedtools.services.push.PushNotificationProvider;
import com.tomtom.speedtools.services.push.domain.Notification;
import com.tomtom.speedtools.services.push.domain.NotificationChannelType;
import com.tomtom.speedtools.services.push.domain.PushToken;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.Set;

import static com.tomtom.speedtools.loghelper.LogHelper.logId;

/**
 * Connector that is able to push messages for Google Cloud Messaging. This should be a singleton in the context of a
 * single thread because it maintains state.
 */
public class GCMConnector implements PushNotificationProvider {
    private static final Logger LOG = LoggerFactory.getLogger(GCMConnector.class);

    private static final String KEY_TYPE = "type";
    private static final String KEY_MESSAGE = "message";
    private static final String KEY_PAYLOAD = "payload";

    private static final int MIN_SECONDS_TO_LIVE = 0;
    private static final int MAX_SECONDS_TO_LIVE = 2419200;

    private final GCMProperties gcmProperties;

    public GCMConnector(@Nonnull final GCMProperties gcmProperties) {
        assert gcmProperties != null;
        this.gcmProperties = gcmProperties;
    }

    @Override
    public PushToken push(@Nonnull final Notification notification, @Nonnull final PushToken pushToken)
            throws PushConnectionException, InvalidPushTokenException {
        assert notification != null;
        assert pushToken != null;

        LOG.debug("push: pushToken={}", pushToken);

        if (!gcmProperties.isEnabled()) {
            LOG.debug("sendPush: notification not sent because connector is disabled, pushToken={}",
                    logId(pushToken.getToken()));
            return pushToken;
        }

        // Create a Sender.
        final Sender sender = new GCMSender(gcmProperties.getEndpoint(), gcmProperties.getApiKey());

        // Create the Message.
        final String pushMessage = notification.getMessage();
        final JsonRenderable jsonPayload = notification.getPayload();
        final Duration timeToLive = notification.getTimeToLive();

        Builder builder = new Builder().addData(KEY_TYPE, notification.getType());
        if (pushMessage != null) {
            builder = builder.addData(KEY_MESSAGE, pushMessage);
        }

        if (jsonPayload != null) {
            builder = builder.addData(KEY_PAYLOAD, jsonPayload.toJson());
        }

        if (timeToLive != null) {
            // The value of this parameter must be a duration from 0 to 2,419,200 seconds.
            final long seconds = timeToLive.getStandardSeconds();
            if ((seconds >= MIN_SECONDS_TO_LIVE) && (seconds <= MAX_SECONDS_TO_LIVE)) {
                //noinspection NumericCastThatLosesPrecision
                builder.timeToLive((int) seconds); // Safe cast.
            }
        }

        final Message message = builder.build();

        // Send the Message.
        final Result result;
        try {
            result = sender.send(message, pushToken.getToken(), gcmProperties.getRetries());
            LOG.debug("push: notification sent, message={}", message.toString());
        } catch (final IOException e) {
            LOG.error("push: unable to send message, pushToken={}", logId(pushToken.getToken()), e);
            throw new PushConnectionException(e);
        }

        if (result.getMessageId() != null) {
            LOG.debug("push: message delivered to GCM, pushToken={}", logId(pushToken.getToken()));
            final String canonicalRegId = result.getCanonicalRegistrationId();
            if (canonicalRegId != null) {

                /**
                 * Same device has more than on registration ID: update database with new (canonical) registration id.
                 * Make sure to replace the registration ID stored the system with this canonical ID, as eventually the ID you're using will stop working.
                 */
                return new PushToken(canonicalRegId);
            } else {
                return pushToken;
            }
        } else {
            final String error = result.getErrorCodeName();
            LOG.error("push: message could not be delivered, pushToken={}, error={}", logId(pushToken.getToken()),
                    error);
            if (error.equals(Constants.ERROR_NOT_REGISTERED)) {

                // Application has been removed from device. Invalidate push token.
                throw new InvalidPushTokenException(pushToken,
                        "Push token is invalid, application has been removed from device");
            }

            if (error.equals(Constants.ERROR_INVALID_REGISTRATION)) {

                // Application registered an invalid registration id. Invalidate push token.
                throw new InvalidPushTokenException(pushToken, "Push token is invalid");
            }

            throw new PushConnectionException("Notification could not be sent: " + error);
        }
    }

    @Nonnull
    @Override
    public NotificationChannelType getSupportedNotificationChannelType() {
        return NotificationChannelType.ANDROID_GCM;
    }

    @Nonnull
    @Override
    public Set<PushToken> getObsoletePushTokens() {
        return Immutables.emptySet();
    }

    @Override
    public boolean canGetObsoletePushTokens() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return gcmProperties.isEnabled();
    }
}