nu.yona.server.sms.PlivoSmsService.java Source code

Java tutorial

Introduction

Here is the source code for nu.yona.server.sms.PlivoSmsService.java

Source

/*******************************************************************************
 * Copyright (c) 2016, 2017 Stichting Yona Foundation This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
 *******************************************************************************/
package nu.yona.server.sms;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import nu.yona.server.exceptions.InvalidDataException;
import nu.yona.server.exceptions.SmsException;
import nu.yona.server.properties.SmsProperties;
import nu.yona.server.properties.YonaProperties;
import nu.yona.server.util.ThymeleafUtil;

@Service
public class PlivoSmsService implements SmsService {
    private static final Logger logger = LoggerFactory.getLogger(PlivoSmsService.class);

    @Autowired
    private YonaProperties yonaProperties;

    @Autowired
    @Qualifier("smsTemplateEngine")
    private TemplateEngine templateEngine;

    @Override
    public void send(String phoneNumber, SmsTemplate messageTemplate, Map<String, Object> templateParameters) {
        Context ctx = ThymeleafUtil.createContext();
        templateParameters.entrySet().stream().forEach(e -> ctx.setVariable(e.getKey(), e.getValue()));

        String message = templateEngine.process(messageTemplate.getName() + ".txt", ctx);
        logger.info("Sending SMS to number '{}'. Message: {}", phoneNumber, message);

        if (!yonaProperties.getSms().isEnabled()) {
            logger.info("SMS sending is disabled. No message has been sent.");
            return;
        }

        try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            String requestMessageStr = createRequestJson(phoneNumber, message);
            HttpPost httpRequest = createHttpRequest(requestMessageStr);
            HttpClientContext httpClientContext = createHttpClientContext();
            HttpResponse httpResponse = httpClient.execute(httpRequest, httpClientContext);
            HttpEntity entity = httpResponse.getEntity();

            int httpResponseCode = httpResponse.getStatusLine().getStatusCode();
            String httpResponseBody = EntityUtils.toString(entity);
            if (httpResponseCode != HttpStatus.SC_ACCEPTED) {
                // @formatter:off
                /*
                 *  In case of an invalid number, the server returns this body:
                 *  {
                 *    "api_id": "d37761b4-b555-11e7-8bc8-065f6a74a84a",
                 *    "error": "'31612345' is not a valid phone number."
                 *  }
                 */
                // @formatter:on
                if (httpResponseBody != null && httpResponseBody.contains("is not a valid phone number")) {
                    throw InvalidDataException.invalidMobileNumber(phoneNumber);
                }
                throw SmsException.smsSendingFailed(httpResponseCode, httpResponseBody);
            }
        } catch (IOException e) {
            throw SmsException.smsSendingFailed(e);
        }

        logger.info("SMS sent succesfully.");
    }

    private HttpClientContext createHttpClientContext() {
        try {
            SmsProperties smsProperties = yonaProperties.getSms();

            URI uri = getPlivoUrl(smsProperties);
            CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
            credentialsProvider.setCredentials(new AuthScope(uri.getHost(), uri.getPort()),
                    new UsernamePasswordCredentials(smsProperties.getPlivoAuthId(),
                            smsProperties.getPlivoAuthToken()));

            HttpClientContext httpClientContext = HttpClientContext.create();
            httpClientContext.setCredentialsProvider(credentialsProvider);
            return httpClientContext;
        } catch (URISyntaxException e) {
            throw SmsException.smsSendingFailed(e);
        }
    }

    private URI getPlivoUrl(SmsProperties smsProperties) throws URISyntaxException {
        return new URI(MessageFormat.format(smsProperties.getPlivoUrl(), smsProperties.getPlivoAuthId()));
    }

    private String createRequestJson(String phoneNumber, String message) {
        try {
            Map<String, Object> requestMessage = new HashMap<>();
            requestMessage.put("src", determineSender(phoneNumber));
            requestMessage.put("dst", phoneNumber.replace("+", ""));
            requestMessage.put("text", message);

            return new ObjectMapper().writeValueAsString(requestMessage);
        } catch (JsonProcessingException e) {
            throw SmsException.smsSendingFailed(e);
        }
    }

    String determineSender(String targetPhoneNumber) {
        for (String prefix : yonaProperties.getSms().getAlphaSenderSupportingCountryCallingCodes().split("\\s")) {
            if (targetPhoneNumber.startsWith(prefix)) {
                return yonaProperties.getSms().getAlphaSenderId();
            }
        }
        return yonaProperties.getSms().getDefaultSenderNumber();
    }

    private HttpPost createHttpRequest(String jsonStr) {
        try {
            SmsProperties smsProperties = yonaProperties.getSms();
            URI uri = getPlivoUrl(smsProperties);

            HttpPost httpRequest = new HttpPost(uri);
            StringEntity requestEntity = new StringEntity(jsonStr, "UTF-8");

            httpRequest.setEntity(requestEntity);
            httpRequest.setHeader("Accept", "application/json");
            httpRequest.setHeader("Content-type", "application/json");

            return httpRequest;
        } catch (URISyntaxException e) {
            throw SmsException.smsSendingFailed(e);
        }
    }
}