mitm.common.sms.transport.clickatell.ClickatellSMSTransport.java Source code

Java tutorial

Introduction

Here is the source code for mitm.common.sms.transport.clickatell.ClickatellSMSTransport.java

Source

/*
 * Copyright (c) 2008-2012, Martijn Brinkers, Djigzo.
 * 
 * This file is part of Djigzo email encryption.
 *
 * Djigzo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License 
 * version 3, 19 November 2007 as published by the Free Software 
 * Foundation.
 *
 * Djigzo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public 
 * License along with Djigzo. If not, see <http://www.gnu.org/licenses/>
 *
 * Additional permission under GNU AGPL version 3 section 7
 * 
 * If you modify this Program, or any covered work, by linking or 
 * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, 
 * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, 
 * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, 
 * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, 
 * wsdl4j-1.6.1.jar (or modified versions of these libraries), 
 * containing parts covered by the terms of Eclipse Public License, 
 * tyrex license, freemarker license, dom4j license, mx4j license,
 * Spice Software License, Common Development and Distribution License
 * (CDDL), Common Public License (CPL) the licensors of this Program grant 
 * you additional permission to convey the resulting work.
 */
package mitm.common.sms.transport.clickatell;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import mitm.common.net.ProxyException;
import mitm.common.net.ProxyInjector;
import mitm.common.properties.HierarchicalPropertiesException;
import mitm.common.sms.SMSTransport;
import mitm.common.sms.SMSTransportException;
import mitm.common.util.Check;
import mitm.common.util.MiscStringUtils;

import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.PostMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * SMS transport for the clickatell SMS gateway. See: http://www.clickatell.com/developers/api_http.php.
 * 
 * 
 * @author Martijn Brinkers
 *
 */
public class ClickatellSMSTransport implements SMSTransport {
    private final static Logger logger = LoggerFactory.getLogger(ClickatellSMSTransport.class);

    /*
     * Reg expr pattern for mathing the response from the clickatell server
     */
    private final static Pattern RESPONSE_PATTERN = Pattern.compile("(?m)^\\s*(.*?)\\s*:\\s*(.*?)\\s*$");

    /*
     * The Clickatell action for sending an SMS 
     */
    private final static String SEND_MESSAGE_ACTION = "sendmsg";

    /*
     * The Clickatell action for getting the balance of the account 
     */
    private final static String GET_BALANCE_ACTION = "getbalance";

    /*
     * Maximum allowed size of a response from the Clickatell SMS gateway. 
     */
    private final static int MAX_RESPONSE_LENGTH = 1024;

    /*
     * The base URL for the clickatell SMS gateway
     */
    private final String clickatellBaseURL;

    /*
     * Will provide APIID, username, password etc.
     */
    private final ClickatellParameterProvider parameterProvider;

    /*
     * Additional HTTP parameters which will be added to the action URL
     */
    private Map<String, String> additionalParameters;

    /*
     * Used to set the proxy for HttpClient
     */
    private final ProxyInjector proxyInjector;

    /*
     * For managing HTTP connections
     */
    private MultiThreadedHttpConnectionManager connectionManager;

    /*
     * The HttpClient (can be used by multiple threads, see http://hc.apache.org/httpclient-3.x/threading.html)
     */
    private HttpClient httpClient;

    /*
     * Delays delivery of SMS to mobile device in minutes (see setDeliveryTime)
     */
    private Integer deliveryTime;

    /*
     * The validity period in minutes (see setValidityPeriod)
     */
    private Integer validityPeriod;

    /*
     * Enables you to send messages longer than a standard message (see setConcatenation)
     */
    private Integer concatenation = 3;

    /*
     * If true the SMS message will be unicode.
     */
    private Boolean unicode;

    public ClickatellSMSTransport(String clickatellBaseURL, ClickatellParameterProvider parameterProvider,
            ProxyInjector proxyInjector) {
        Check.notNull(clickatellBaseURL, "clickatellBaseURL");
        Check.notNull(parameterProvider, "parameterProvider");

        clickatellBaseURL = clickatellBaseURL.trim();

        if (!clickatellBaseURL.endsWith("/")) {
            clickatellBaseURL = clickatellBaseURL + "/";
        }

        this.clickatellBaseURL = clickatellBaseURL;
        this.parameterProvider = parameterProvider;
        this.proxyInjector = proxyInjector;
    }

    /**
     * Delays delivery of SMS to mobile device in minutes relative to the time at which the SMS was
     * received by the gateway. This should be greater than 10 minutes for best effect. Smaller time
     * frames may be delivered too soon.
     * 
     * The upper limit is 7 days, or 10080 minutes.
     * 
     * Default: 0
     */
    public void setDeliveryTime(Integer minutes) {
        if (minutes != null && (minutes < 0 || minutes > 10080)) {
            throw new IllegalArgumentException("DeliveryTime must be >= 0 and <= 10080");
        }

        this.deliveryTime = minutes;
    }

    /**
     * The validity period in minutes relative to the time at which the SMS was received by our gateway. 
     * The message will not be delivered if it is still queued on our gateway after this time period.
     *
     * @param minutes
     */
    public void setValidityPeriod(Integer minutes) {
        if (minutes != null && (minutes < 1 || minutes > 10080)) {
            throw new IllegalArgumentException("DeliveryTime must be >= 1 and <= 1440");
        }

        this.validityPeriod = minutes;
    }

    /**
     * Enables you to send messages longer than a standard message.
     * 
     * If this value is set to 1, 2 or 3 the message will span across 1, 2 or 3 SMS messages where applicable.
     * One text SMS will be sent for every 160 characters or 140 bytes. If a message is concatenated, it
     * reduces the number of characters contained in each message by 7. With 8-bit concatenated messages,
     * each SMS can support up to 160 bytes including the UDH headers.
     * Please be aware that a single Unicode SMS can only contain a maximum of 70 characters. 8-Bit
     * messages will be split over multiple messages, where necessary, irrespective of whether the flag for
     * concatenated messages has been set. If a Unicode message is concatenated, it reduces the number of
     * characters contained in each message part by 3.
     *
     * 1 No concatenation: only 1 message.
     * 2 Concatenate a maximum of 2 messages.
     * 3 Concatenate a maximum of 3 messages.
     * N Concatenate a maximum of N messages.
     * (Delivery is dependent on mobile and gateway. A maximum of 3 is recommended).
     * 
     * default: 3
     * 
     * @param concatenation
     */
    public void setConcatenation(Integer concatenation) {
        if (concatenation != null && concatenation < 0) {
            throw new IllegalArgumentException("concatenation must be >= 0");
        }
    }

    /**
     * If true the SMS message will be unicode.
     * 
     * @param unicode
     */
    public void setUnicode(Boolean unicode) {
        this.unicode = unicode;
    }

    public void setMaxConnectionsPerHost(int maxConnectionsPerHost) {
        getConnectionManager().getParams().setMaxConnectionsPerHost(HostConfiguration.ANY_HOST_CONFIGURATION,
                maxConnectionsPerHost);
    }

    public void setMaxTotalConnections(int maxTotalConnections) {
        getConnectionManager().getParams().setMaxTotalConnections(maxTotalConnections);
    }

    @Override
    public String getName() {
        return ClickatellSMSTransport.class.getCanonicalName();
    }

    /**
     * Sends the message to the given number.
     * 
     * SMS messages need to be sent in the standard international format, with country code followed by
     * number. No leading zero to the number and no special characters such as "+" or spaces must be used.
     * For example, a number in the UK being 07901231234 should be changed to 447901231234.
     * If have you set the preferred dial prefix preference within your client account after logging in on-line, any
     * mobile numbers starting with zero will have the zero stripped and replaced with your default prefix. If the
     * mobile number does not have a zero, the default prefix will not be changed.
     *
     */
    @Override
    public void sendSMS(String phoneNumber, String message) throws SMSTransportException {
        if (phoneNumber == null) {
            throw new SMSTransportException("phoneNumber is empty.");
        }

        if (message == null) {
            throw new SMSTransportException("Message is empty.");
        }

        PostMethod postMethod = new PostMethod(getURL(SEND_MESSAGE_ACTION));

        addParameterProviderParameters(postMethod);

        addParameter(postMethod, Parameter.DESTINATION_ADDRESS, phoneNumber);
        addParameter(postMethod, Parameter.TEXT, message);

        if (deliveryTime != null) {
            addParameter(postMethod, Parameter.DELIVERY_TIME, deliveryTime.toString());
        }

        if (validityPeriod != null) {
            addParameter(postMethod, Parameter.VALIDITY_PERIOD, validityPeriod.toString());
        }

        if (concatenation != null) {
            addParameter(postMethod, Parameter.CONCATENATION, concatenation.toString());
        }

        if (unicode != null) {
            addParameter(postMethod, Parameter.UNICODE_MESSAGE, unicode ? "1" : "0");
        }

        /*
         * Add additional HTTP parameters
         */
        if (additionalParameters != null) {
            for (Entry<String, String> entry : additionalParameters.entrySet()) {
                addParameter(postMethod, entry.getKey(), entry.getValue());
            }
        }

        Map<String, String> responseMap = getResponse(postMethod);

        String messageID = responseMap.get("ID");

        if (messageID == null) {
            throw new SMSTransportException("ID not found. SMS was probably not sent.");
        }

        logger.info("Message sent with ID: " + messageID);
    }

    public float getBalance() throws SMSTransportException {
        PostMethod postMethod = new PostMethod(getURL(GET_BALANCE_ACTION));

        addParameterProviderParameters(postMethod);

        Map<String, String> responseMap = getResponse(postMethod);

        String credit = responseMap.get("CREDIT");

        logger.debug("Credit: " + credit);

        float value = -1;

        try {
            if (credit != null) {
                value = Float.parseFloat(credit);
            } else {
                logger.warn("Credit value does not exist.");
            }
        } catch (NumberFormatException e) {
            logger.error("Credit " + credit + " is not a float.");
        }

        return value;
    }

    private Map<String, String> getResponse(PostMethod postMethod) throws SMSTransportException {
        try {
            Map<String, String> responseMap = new HashMap<String, String>();

            try {
                getHttpClient().executeMethod(postMethod);

                byte[] rawResponse = postMethod.getResponseBody(MAX_RESPONSE_LENGTH);

                if (rawResponse != null) {
                    String response = MiscStringUtils.toAsciiString(rawResponse);

                    logger.debug("Response: " + response);

                    responseMap = parseResponse(response);

                    String error = responseMap.get("ERR");

                    if (error != null) {
                        throw new SMSTransportException("Error sending SMS. Error: " + error);
                    }
                }
            } finally {
                postMethod.releaseConnection();
            }

            return responseMap;
        } catch (HttpException e) {
            throw new SMSTransportException(e);
        } catch (ProxyException e) {
            throw new SMSTransportException(e);
        } catch (IOException e) {
            throw new SMSTransportException(e);
        }
    }

    private Map<String, String> parseResponse(String response) {
        Map<String, String> responses = new HashMap<String, String>();

        if (response != null) {
            Matcher matcher = RESPONSE_PATTERN.matcher(response.trim());

            while (matcher.find()) {
                responses.put(matcher.group(1).toUpperCase(), matcher.group(2));
            }
        }

        if (responses.size() == 0) {
            logger.warn("No valid responses found. Response: " + response);
        }

        return responses;
    }

    private String getURL(String action) {
        return clickatellBaseURL + action;
    }

    private void addParameter(PostMethod postMethod, Parameter parameter, String value) {
        addParameter(postMethod, parameter.getName(), value);
    }

    private void addParameter(PostMethod postMethod, String parameter, String value) {
        if (parameter == null) {
            return;
        }

        /*
         * Null values are now allowed
         */
        if (value == null) {
            value = "";
        }

        postMethod.addParameter(parameter, value);
    }

    private void addParameterProviderParameters(PostMethod postMethod) throws SMSTransportException {
        try {
            ClickatellParameters parameters = parameterProvider.getParameters();

            addParameter(postMethod, Parameter.API_PRODUCT_ID, parameters.getAPIID());
            addParameter(postMethod, Parameter.USERNAME, parameters.getUser());
            addParameter(postMethod, Parameter.PASSWORD, parameters.getPassword());

            if (parameters.getFrom() != null) {
                addParameter(postMethod, Parameter.SOURCE_ADDRESS, parameters.getFrom());
            }
        } catch (HierarchicalPropertiesException e) {
            throw new SMSTransportException(e);
        }
    }

    private synchronized MultiThreadedHttpConnectionManager getConnectionManager() {
        if (connectionManager == null) {
            connectionManager = new MultiThreadedHttpConnectionManager();
        }

        return connectionManager;
    }

    private synchronized HttpClient getHttpClient() throws ProxyException {
        if (httpClient == null) {
            httpClient = new HttpClient(getConnectionManager());
        }

        if (proxyInjector != null) {
            proxyInjector.setProxy(httpClient);
        }

        return httpClient;
    }

    public void setAdditionalParameters(Map<String, String> parameters) {
        additionalParameters = Collections.synchronizedMap(new HashMap<String, String>());

        additionalParameters.putAll(parameters);
    }

    public static void main(String[] args) throws SMSTransportException {
        StaticClickatellParameterProvider parameterProvider = new StaticClickatellParameterProvider("",
                "m.brinkers", "", null);

        ClickatellSMSTransport transport = new ClickatellSMSTransport("https://api.clickatell.com/http",
                parameterProvider, null);

        System.out.println(transport.getBalance());
        //transport.sendSMS("31641640044", "test sms 2");
        System.out.println(transport.getBalance());
    }
}