/* * FrontlineSMS <http://www.frontlinesms.com> * Copyright 2007, 2008 kiwanja * * This file is part of FrontlineSMS. * * FrontlineSMS is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * FrontlineSMS 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 Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with FrontlineSMS. If not, see <http://www.gnu.org/licenses/>. */ package net.frontlinesms.messaging.sms.internet; import java.util.*; import net.frontlinesms.*; import net.frontlinesms.data.domain.*; import net.frontlinesms.data.domain.FrontlineMessage.Status; import net.frontlinesms.email.receive.*; import net.frontlinesms.messaging.Provider; import net.frontlinesms.messaging.sms.properties.OptionalSection; import net.frontlinesms.messaging.sms.properties.PasswordString; import net.frontlinesms.messaging.sms.properties.PhoneSection; import net.frontlinesms.ui.handler.settings.SmsInternetServiceSettingsHandler; import org.apache.log4j.Logger; import org.smslib.CIncomingMessage; import org.smslib.util.GsmAlphabet; import org.smslib.util.HexUtils; import org.smslib.util.TpduUtils; import IntelliSoftware.SMSGateway.SDK.IntelliSMSJavaSDK.*; /** * Implements the IntelliSms Internet service. * * @author Carlos Eduardo Endler Genz * @date 31/01/2009 */ @Provider(name = "IntelliSms", icon = "/icons/sms_http.png") public class IntelliSmsInternetService extends AbstractSmsInternetService implements EmailReceiveProcessor { //> STATIC CONSTANTS /** Default source port to use for an outgoing binary SMS message */ private static final int DEFAULT_SOURCE_PORT = 0; /** IntelliSMS defines its maximum SMS parts as 10. Specifying more than this will lead to a ERR:PARAMETER_INVALID when * querying the HTTP interface. */ private static final int MAX_SMS_PARTS = 10; /** Prefix attached to every property name. */ private static final String PROPERTY_PREFIX = "smsdevice.internet.intellisms."; protected static final String PROPERTY_USERNAME = PROPERTY_PREFIX + "username"; protected static final String PROPERTY_PASSWORD = PROPERTY_PREFIX + "password"; protected static final String PROPERTY_FROM_MSISDN = PROPERTY_PREFIX + "from.msisdn"; protected static final String PROPERTY_SSL = PROPERTY_PREFIX + "ssl"; protected static final String PROPERTY_RECEIVING_EMAIL_HOST = PROPERTY_PREFIX + "email.host"; protected static final String PROPERTY_RECEIVING_EMAIL_HOST_PORT = PROPERTY_PREFIX + "email.host.port"; protected static final String PROPERTY_RECEIVING_EMAIL_HOST_SSL = PROPERTY_PREFIX + "email.host.ssl"; protected static final String PROPERTY_RECEIVING_EMAIL_USERNAME = PROPERTY_PREFIX + "email.username"; protected static final String PROPERTY_RECEIVING_EMAIL_PASSWORD = PROPERTY_PREFIX + "email.password"; private static final String PROPERTY_PROXY_ENABLED = PROPERTY_PREFIX + "proxy.enabled"; private static final String PROPERTY_PROXY_ADDRESS = PROPERTY_PREFIX + "proxy.address"; private static final String PROPERTY_PROXY_USERNAME = PROPERTY_PREFIX + "proxy.username"; private static final String PROPERTY_PROXY_PASSWORD = PROPERTY_PREFIX + "proxy.password"; /** * Currently the IntelliSMS code includes proxy options, but no actual implementation to make this work. * If they do ever implement them, we can activate, or better remove, this constant. */ private static final boolean PROXIES_SUPPORTED = false; /** Logging object */ private static Logger LOG = FrontlineUtils.getLogger(IntelliSmsInternetService.class); //> INSTANCE PROPERTIES private IntelliSMS intelliSMS; private boolean connected; /** * Initialises the intellisms sdk gateway. */ private synchronized void initGateway() { intelliSMS = new IntelliSMS(); intelliSMS.Username = getUsername(); intelliSMS.Password = getPassword(); intelliSMS.MaxConCatMsgs = MAX_SMS_PARTS; if (isEncrypted()) { intelliSMS.PrimaryGateway="https://www.intellisoftware.co.uk"; intelliSMS.BackupGateway="https://www.intellisoftware2.co.uk"; } else { intelliSMS.PrimaryGateway="http://www.intellisoftware.co.uk"; intelliSMS.BackupGateway="http://www.intellisoftware2.co.uk"; } } /** * Verify if we this services has received messages. * @return whether receive was successful */ protected synchronized void receiveSms() throws SmsInternetServiceReceiveException { EmailReceiver receiver = new EmailReceiver(this); receiver.setHostAddress(getEmailHost()); receiver.setHostPassword(getEmailPassword()); receiver.setHostPort(getEmailHostPort()); receiver.setHostUsername(getEmailUsername()); receiver.setUseSsl(getEmailHostSSL()); receiver.setProtocol(EmailReceiveProtocol.POP3); try { receiver.receive(); } catch(EmailReceiveException ex) { throw new SmsInternetServiceReceiveException(ex); } } public void processMessage(javax.mail.Message message, Date date) { try { // We've got a message, so try and find it's text content. String messageText = EmailReceiveUtils.getMessageText(message); // For now, if the message text was null then set it to an empty string if(messageText == null) messageText = ""; this.processPopMessage(EmailReceiveUtils.getSender(message), message.getSentDate().getTime(), message.getSubject(), messageText); } catch (Exception ex) { LOG.warn("Error processing email."); } } public void processPopMessage(String sender, long messageSentDate, String subject, String messageText) { if (subject.startsWith("Message from ")) { String from = subject.split(" ")[2]; LOG.debug("Message from [" + from + "]"); LOG.debug("The message content [" + messageText + "]"); // Sender might be blank in this case. if (!from.equals(sender)) { sender = from; } CIncomingMessage cMessage = new CIncomingMessage(messageSentDate, sender, messageText); smsListener.incomingMessageEvent(this, cMessage); } else if (subject.startsWith("Status report for message sent to")) { // We've got a delivery report // TODO we need to get the msg id. Since it is not in the email. // We can get delivery reports polling IntelliSMS server with // the messages id. LOG.trace("Received status report. Not currently handled."); } else { LOG.trace("Unrecognized message subject '"+subject+"'"); } } /** * Send an SMS message using this phone handler. * @param message The message to be sent. */ protected synchronized void sendSmsDirect(FrontlineMessage message) { LOG.trace("ENTER"); LOG.debug("Sending [" + message.getTextContent() + "] to [" + message.getRecipientMsisdn() + "]"); try { ResultCodes code; if (message.isBinaryMessage()) { code = sendBinarySms(message); } else if(!GsmAlphabet.areAllCharactersValidGSM(message.getTextContent())) { code = sendUcs2Sms(message); } else { if ( (message.getTextContent().length() / FrontlineMessage.SMS_LENGTH_LIMIT) > MAX_SMS_PARTS) { LOG.error("Message too long. [" + message.getTextContent().length() + "] chars."); } SendStatusCollection results = intelliSMS.SendMessage(new String[] {message.getRecipientMsisdn()}, message.getTextContent(), getMsisdn()); code = results.OverallResultCode; } LOG.debug("Code is [" + code + "]"); if (code == ResultCodes.OK) { message.setStatus(Status.SENT); LOG.debug("Message [" + message + "] was sent!"); } else { if (code == ResultCodes.InsufficientCredit) { setStatus(SmsInternetServiceStatus.LOW_CREDIT, Integer.toString(getRemainingCredit())); } message.setStatus(Status.FAILED); LOG.debug("Message [" + message + "] was not sent. Cause: [" + code + "]"); } } catch (IntelliSMSException e) { message.setStatus(Status.FAILED); LOG.debug("Failed to send message [" + message + "]: " + e.getResultCode(), e); LOG.info("Failed to send message: " + e.getResultCode()); if(ResultCodes.InsufficientCredit.equals(e.getResultCode())) { int remainingCredit; try { remainingCredit = getRemainingCredit(); } catch (IntelliSMSException e1) { remainingCredit = -1; } this.stopThisThing(); setStatus(SmsInternetServiceStatus.LOW_CREDIT, Integer.toString(remainingCredit)); } else { this.stopThisThing(); this.setStatus(SmsInternetServiceStatus.DISCONNECTED, e.getResultCode() + ": " + e.getMessage()); } } finally { if (smsListener != null) { smsListener.outgoingMessageEvent(this, message); } } LOG.trace("EXIT"); } private ResultCodes sendBinarySms(FrontlineMessage message) throws IntelliSMSException { LOG.trace("ENTER"); byte[][] messagePayloads = TpduUtils.getPayloads(message.getBinaryContent(), DEFAULT_SOURCE_PORT, message.getRecipientSmsPort()); int totalParts = messagePayloads.length; LOG.debug("Total parts [" + totalParts + "]"); ResultCodes ret = ResultCodes.OK; for (int i = 1; i <= totalParts; i++) { SendStatusCollection results = intelliSMS.SendBinaryMessage( new String[]{ message.getRecipientMsisdn() }, HexUtils.encode(TpduUtils.generateUDH(i, totalParts, 0, DEFAULT_SOURCE_PORT, message.getRecipientSmsPort())), HexUtils.encode(messagePayloads[i-1]), getMsisdn()); ret = results.OverallResultCode; LOG.debug("Executing part [" + i + "], result code is [" + ret + "]."); } LOG.trace("EXIT"); return ret; } private ResultCodes sendUcs2Sms(FrontlineMessage message) throws IntelliSMSException { LOG.trace("ENTER"); String[] messageParts = TpduUtils.splitText_ucs2(message.getTextContent(), false); int totalParts = messageParts.length; LOG.debug("Total parts [" + totalParts + "]"); SendStatusCollection results = intelliSMS.SendUnicodeMessage(new String[]{message.getRecipientMsisdn()}, message.getTextContent(), getMsisdn()); ResultCodes ret = results.OverallResultCode; LOG.trace("EXIT"); return ret; } /** * Starts the service. Normally we initialise the gateway. * @throws SmsInternetServiceInitialisationException */ protected void init() throws SmsInternetServiceInitialisationException { initGateway(); try { getRemainingCredit(); connected = true; this.setStatus(SmsInternetServiceStatus.CONNECTED, null); } catch (IntelliSMSException e) { LOG.debug("Could not connect", e); connected = false; this.setStatus(SmsInternetServiceStatus.FAILED_TO_CONNECT, e.getMessage()); throw new SmsInternetServiceInitialisationException(e); } } /** * Get the default properties for this class. */ public LinkedHashMap<String, Object> getPropertiesStructure() { LinkedHashMap<String, Object> defaultSettings = new LinkedHashMap<String, Object>(); defaultSettings.put(PROPERTY_USERNAME, ""); defaultSettings.put(PROPERTY_PASSWORD, new PasswordString("")); defaultSettings.put(PROPERTY_FROM_MSISDN, new PhoneSection("")); defaultSettings.put(PROPERTY_SSL, Boolean.FALSE); defaultSettings.put(PROPERTY_USE_FOR_SENDING, Boolean.TRUE); // Proxy properties if(PROXIES_SUPPORTED) { OptionalSection section = new OptionalSection(); section.setValue(false); section.addDependency(PROPERTY_PROXY_ADDRESS, ""); section.addDependency(PROPERTY_PROXY_USERNAME, ""); section.addDependency(PROPERTY_PROXY_PASSWORD, new PasswordString("")); defaultSettings.put(PROPERTY_PROXY_ENABLED, section); } // Receiving properties OptionalSection section = new OptionalSection(); section.setValue(false); section.addDependency(PROPERTY_RECEIVING_EMAIL_HOST, ""); section.addDependency(PROPERTY_RECEIVING_EMAIL_HOST_PORT, new Integer(995)); section.addDependency(PROPERTY_RECEIVING_EMAIL_HOST_SSL, Boolean.TRUE); section.addDependency(PROPERTY_RECEIVING_EMAIL_USERNAME, ""); section.addDependency(PROPERTY_RECEIVING_EMAIL_PASSWORD, new PasswordString("")); defaultSettings.put(PROPERTY_USE_FOR_RECEIVING, section); return defaultSettings; } /** * Gets the MSISDN that numbers sent from this service will appear to be from. */ public String getMsisdn() { return getPropertyValue(PROPERTY_FROM_MSISDN, PhoneSection.class).getValue(); } /** * Gets an identifier for this instance of {@link SmsInternetService}. Usually this * will be the username used to login with the provider, or a similar identifer on * the service. */ public String getIdentifier() { return this.getUsername(); } /** * @return The property value of {@value #PROPERTY_USERNAME} */ private String getUsername() { return getPropertyValue(PROPERTY_USERNAME, String.class); } /** * @return The property value of {@value #PROPERTY_PASSWORD} */ private String getPassword() { return getPropertyValue(PROPERTY_PASSWORD, PasswordString.class).getValue(); } /** * @return The property value of {@value #PROPERTY_RECEIVING_EMAIL_PASSWORD} */ private String getEmailPassword() { return getPropertyValue(PROPERTY_RECEIVING_EMAIL_PASSWORD, PasswordString.class).getValue(); } /** * @return The property value of {@value #PROPERTY_RECEIVING_EMAIL_HOST} */ private String getEmailHost() { return getPropertyValue(PROPERTY_RECEIVING_EMAIL_HOST, String.class); } /** * @return The property value of {@value #PROPERTY_RECEIVING_EMAIL_HOST_PORT} */ private Integer getEmailHostPort() { return getPropertyValue(PROPERTY_RECEIVING_EMAIL_HOST_PORT, Integer.class); } /** * @return The property value of {@value #PROPERTY_RECEIVING_EMAIL_HOST_SSL} */ private boolean getEmailHostSSL() { return getPropertyValue(PROPERTY_RECEIVING_EMAIL_HOST_SSL, Boolean.class); } /** * @return The property value of {@value #PROPERTY_RECEIVING_EMAIL_USERNAME} */ private String getEmailUsername() { return getPropertyValue(PROPERTY_RECEIVING_EMAIL_USERNAME, String.class); } /** * Checks if the service is currently connected. * TODO could rename this isLive(). * @return */ public boolean isConnected() { return connected; } /** * @return The remaining credits of this accounts. * @throws IntelliSMSException If something goes wrong connecting to intellisms. */ private int getRemainingCredit() throws IntelliSMSException { return intelliSMS.GetBalance(); } /** * Check if this service is encrypted using SSL. */ public boolean isEncrypted() { return getPropertyValue(PROPERTY_SSL, Boolean.class); } /** * Check if this device is being used to receive SMS messages. * Our implementation of IntelliSms can support SMS receiving. */ public boolean isUseForReceiving() { return getPropertyValue(PROPERTY_USE_FOR_RECEIVING, Boolean.class); } /** * Checks if this device is being used to send SMS messages. */ public boolean isUseForSending() { return getPropertyValue(PROPERTY_USE_FOR_SENDING, Boolean.class); } /** * Set this device to be used for receiving messages. */ public void setUseForReceiving(boolean use) { setProperty(PROPERTY_USE_FOR_RECEIVING, new Boolean(use)); } /** * Set this device to be used for sending SMS messages. */ public void setUseForSending(boolean use) { this.setProperty(PROPERTY_USE_FOR_SENDING, new Boolean(use)); } /** * We do support receive through IntelliSMS (pop3). * @return <code>true</code> */ public boolean supportsReceive() { return true; } /** * Check whether this device actually supports sending binary sms. */ public boolean isBinarySendingSupported() { return true; } /** * TODO this needs testing. If it works, this method can return true. If it doesn't work, * this comment can be replaced with a more descriptive one. */ public boolean isUcs2SendingSupported() { return false; } @Override protected void deinit() { connected = false; this.setStatus(SmsInternetServiceStatus.DISCONNECTED, null); } public String getServiceName() { return this.getUsername() + UI_NAME_SEPARATOR + SmsInternetServiceSettingsHandler.getProviderName(getClass()); } public String getPort() { return null; } public String getDisplayPort() { // TODO Auto-generated method stub return null; } //> STATIC HELPER METHODS }