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

com.tomtom.speedtools.services.sms.implementation.nexmo.Nexmo Maven / Gradle / Ivy

Go to download

Provides interfaces for SMS services, with implementations for MessageBird and Nexmo, to send text messages to mobile phones.

There is a newer version: 3.4.4
Show newest version
/*
 * Copyright (C) 2012-2017. 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.sms.implementation.nexmo;

import com.tomtom.speedtools.json.Json;
import com.tomtom.speedtools.services.sms.SMSDeliveryReportListener.DeliveryStatus;
import com.tomtom.speedtools.services.sms.SMSDeliveryReportParameterException;
import com.tomtom.speedtools.services.sms.SMSDeliveryReportProcessor;
import com.tomtom.speedtools.services.sms.SMSProviderConnector;
import com.tomtom.speedtools.services.sms.implementation.ProviderNames;
import com.tomtom.speedtools.services.sms.implementation.ProviderRanking;
import com.tomtom.speedtools.services.sms.implementation.nexmo.dto.NexmoMessage;
import com.tomtom.speedtools.services.sms.implementation.nexmo.dto.NexmoMessageResponse;
import org.jboss.resteasy.client.ClientResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.ws.rs.core.Response;
import java.util.Map;

/**
 * Provides SMS functionality through Nexmo (www.nexmo.com).
 *
 * The API documentation can be found here: http://www.nexmo.com/documentation
 */
public class Nexmo implements SMSProviderConnector {
    @Nonnull
    private static final Logger LOG = LoggerFactory.getLogger(Nexmo.class);

    private static final String STATUS_REPORT_REQUIRED = "1";
    private static final String CLIENT_REF_PARAMETER = "client-ref";
    private static final String STATUS_REPORT_PARAMETER = "status";
    private static final String DELIVERED_STATUS = "delivered";
    private static final String BUFFERED_STATUS = "buffered";
    private static final String FAILED_STATUS = "failed";
    private static final String TYPE = "text";

    @Nonnull
    private final NexmoResource nexmoResource;
    @Nonnull
    private final String userName;
    @Nonnull
    private final String password;
    @Nonnull
    private final String sender;

    /**
     * Constructor.
     *
     * @param nexmoResource   The Resteasy resource to do REST calls to Nexmo.
     * @param nexmoProperties Properties for Nexmo.
     */
    @Inject
    public Nexmo(@Nonnull final NexmoResource nexmoResource, @Nonnull final NexmoProperties nexmoProperties) {
        assert nexmoResource != null;
        assert nexmoProperties != null;

        this.nexmoResource = nexmoResource;
        userName = nexmoProperties.getUserName();
        password = nexmoProperties.getPassword();
        sender = nexmoProperties.getSender();
    }

    @Nonnull
    @Override
    public Status sendTextMessage(
            @Nonnull final String recipient,
            @Nonnull final String message,
            @Nullable final Long referenceNumber) {
        assert recipient != null;
        assert message != null;

        ClientResponse response = null;
        try {

            // Send the message.
            LOG.debug("sendTextMessage: recipient={}, ref={}, message: {}",
                    recipient, referenceNumber, message);
            response =
                    nexmoResource.sendMessage(userName, password, sender, recipient, TYPE, message, STATUS_REPORT_REQUIRED,
                            referenceNumber);

            // Process the response.
            if (response.getResponseStatus() == Response.Status.OK) {

                final NexmoMessageResponse nexmoMessageResponse = response.getEntity();

                // Process messages in the response. Nexmo supports splitting into multiple messages, but we do not.
                for (final NexmoMessage nexmoMessage : nexmoMessageResponse.getMessages()) {
                    switch (nexmoMessage.getStatus()) {

                        case SUCCESS:
                            return Status.SENT;

                        case THROTTLED:
                            return Status.THROTTLE;

                        case MISSING_PARAMS:
                            LOG.error(
                                    "sendTextMessage: Missing parameters. error={}, recipient={}, message={}, ref={}.",
                                    nexmoMessage.getErrorText(), recipient, message, referenceNumber);
                            return Status.FAILED_PERMANENTLY;

                        case INVALID_PARAMS:
                            LOG.warn(
                                    "sendTextMessage: Invalid parameters. error={}, recipient={}, message={}, ref={}.",
                                    nexmoMessage.getErrorText(), recipient, message, referenceNumber);
                            return Status.INVALID_PARAMETERS;

                        case INVALID_CREDENTIALS:
                            LOG.error("sendTextMessage: Invalid credentials. error={}, userName: {}.",
                                    nexmoMessage.getErrorText(), userName);
                            return Status.FAILED_PERMANENTLY;

                        case INTERNAL_ERROR:
                            LOG.warn(
                                    "sendTextMessage: Internal error at Nexmo. error={}, recipient={}, message={}, ref={}.",
                                    nexmoMessage.getErrorText(), recipient, message);
                            return Status.FAILED;

                        case INVALID_MESSAGE:
                            LOG.info(
                                    "sendTextMessage: Invalid message. error={}, recipient={}, message={}, ref={}.",
                                    nexmoMessage.getErrorText(), recipient, message, referenceNumber);
                            return Status.INVALID_PARAMETERS;

                        case NUMBER_BARRED:
                            LOG.warn("sendTextMessage: Number barred. error={}, recipient={}.",
                                    nexmoMessage.getErrorText(), recipient);
                            return Status.INVALID_PARAMETERS;


                        case PARTNER_ACCOUNT_BARRED:
                            LOG.warn("sendTextMessage: Partner account barred. error={}.",
                                    nexmoMessage.getErrorText());
                            return Status.TEMPORARILY_UNAVAILABLE;


                        case PARTNER_QUOTA_EXCEEDED:
                            LOG.warn("sendTextMessage: Partner quota exceeded. error={}.",
                                    nexmoMessage.getErrorText());
                            return Status.TEMPORARILY_UNAVAILABLE;

                        case TOO_MANY_EXISTING_BINDS:
                            LOG.warn("sendTextMessage: Too many connections. error={}.",
                                    nexmoMessage.getErrorText());
                            return Status.THROTTLE;

                        case ACCOUNT_NOT_ENABLED_FOR_REST:
                            LOG.warn("sendTextMessage: Account not enabled for REST. Error={}.",
                                    nexmoMessage.getErrorText());
                            return Status.TEMPORARILY_UNAVAILABLE;

                        case MESSAGE_TOO_LONG:
                            LOG.info("sendTextMessage: Message too long. error={}, recipient={}, message={}, ref={}.",
                                    nexmoMessage.getErrorText(), recipient, message, referenceNumber);
                            return Status.INVALID_PARAMETERS;

                        case INVALID_SENDER_ADDRESS:
                            LOG.error("sendTextMessage: Invalid sender address. error={}, sender={}.",
                                    nexmoMessage.getErrorText(), sender);
                            return Status.FAILED_PERMANENTLY;

                        case INVALID_TTL:
                            LOG.error("sendTextMessage: Invalid TTL. error={}", nexmoMessage.getErrorText());
                            return Status.FAILED_PERMANENTLY;

                        default:
                            LOG.error("sendTextMessage: Unknown status code. error={}", nexmoMessage.getErrorText());
                            assert false;
                            break;
                    }
                }
            }
        } catch (final Throwable t) {
            // Resteasy throws several runtime exceptions, but it is not exactly clear which.
            LOG.error("sendTextMessage: Failed to send message to Nexmo. recipient={}, message={}, ref={}.",
                    recipient, message, referenceNumber, t);
        } finally {
            if (response != null) {
                // Always release the connection.
                response.releaseConnection();
            }
        }

        return Status.FAILED;
    }

    @Nonnull
    @Override
    public String getProviderName() {
        return ProviderNames.NEXMO.toString();
    }

    @Override
    public int getDefaultRanking() {
        return ProviderRanking.NEXMO;
    }

    @Override
    @Nonnull
    public SMSDeliveryReportProcessor getProcessor(@Nonnull final Map parameterMap)
            throws SMSDeliveryReportParameterException {
        assert parameterMap != null;

        // Process parameters.
        final int referenceNumber = getReferenceNumber(parameterMap);
        final DeliveryStatus deliveryStatus = getDeliveryStatus(parameterMap);

        // Return a processor based on these parameters.
        return new SMSDeliveryReportProcessor() {

            @Override
            public boolean skip() {
                return deliveryStatus == null;
            }

            @Override
            public long getReferenceNumber() {
                assert deliveryStatus != null;

                return referenceNumber;
            }

            @Override
            @Nonnull
            public DeliveryStatus getDeliveryStatus() {
                assert deliveryStatus != null;

                return deliveryStatus;
            }
        };
    }

    @Nullable
    private static DeliveryStatus getDeliveryStatus(
            @Nonnull final Map parameterMap) throws SMSDeliveryReportParameterException {
        assert parameterMap != null;

        // Obtain status from the parameter map.
        final String[] clientRef = parameterMap.get(STATUS_REPORT_PARAMETER);
        if ((clientRef != null) && (clientRef.length == 1)) {
            if (clientRef[0].equalsIgnoreCase(DELIVERED_STATUS)) {
                return DeliveryStatus.DELIVERED;
            } else if (clientRef[0].equalsIgnoreCase(BUFFERED_STATUS)) {
                return DeliveryStatus.BUFFERED;
            } else if (clientRef[0].equalsIgnoreCase(FAILED_STATUS)) {
                return DeliveryStatus.FAILED;
            } else {
                // Ignore this status.
                return null;
            }
        } else {
            LOG.error("getDeliveryStatus: Incorrect number of values. parameter={}, values={}.",
                    STATUS_REPORT_PARAMETER, clientRef);
            throw new SMSDeliveryReportParameterException(
                    "Parameter " + STATUS_REPORT_PARAMETER + " contains incorrect number of values. Values: " +
                            Json.toStringJson(clientRef));
        }
    }

    private static int getReferenceNumber(@Nonnull final Map parameterMap)
            throws SMSDeliveryReportParameterException {
        assert parameterMap != null;

        // Obtain reference from the parameter map.
        final String[] clientRef = parameterMap.get(CLIENT_REF_PARAMETER);
        if ((clientRef != null) && (clientRef.length == 1)) {
            try {
                return Integer.parseInt(clientRef[0]);
            } catch (final NumberFormatException ex) {
                LOG.error("getReferenceNumber: Failed to parse parameter. parameter={}, value={}",
                        CLIENT_REF_PARAMETER, clientRef[0], ex);
                throw new SMSDeliveryReportParameterException(ex);
            }
        } else {
            LOG.error("getReferenceNumber: Incorrect number of values. parameter={}, values={}.",
                    CLIENT_REF_PARAMETER, clientRef);
            throw new SMSDeliveryReportParameterException(
                    "Parameter " + CLIENT_REF_PARAMETER + " contains incorrect number of values. Values: " +
                            Json.toStringJson(clientRef));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy