Java tutorial
/* * 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()); } }