/*
* 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.LinkedHashMap;
import net.frontlinesms.*;
import net.frontlinesms.data.domain.FrontlineMessage;
import net.frontlinesms.data.domain.FrontlineMessage.Status;
import net.frontlinesms.messaging.Provider;
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.ReceiveNotSupportedException;
import org.smslib.v3.*;
import org.smslib.v3.http.ClickatellHTTPGateway;
/**
* Implements the Clickatell Internet service.
*
* @author Carlos Eduardo Endler Genz
* @date 26/01/2009
*/
@Provider(name = "Clickatell", icon = "/icons/sms_http.png")
public class ClickatellInternetService extends AbstractSmsInternetService {
/** Prefix attached to every property name. */
private static final String PROPERTY_PREFIX = "smsdevice.internet.clickatell.";
protected static final String PROPERTY_USERNAME = PROPERTY_PREFIX + "username";
protected static final String PROPERTY_PASSWORD = PROPERTY_PREFIX + "password";
protected static final String PROPERTY_API = PROPERTY_PREFIX + "api";
protected static final String PROPERTY_FROM_MSISDN = PROPERTY_PREFIX + "from.msisdn";
protected static final String PROPERTY_SSL = PROPERTY_PREFIX + "ssl";
/** Logging object */
private static Logger LOG = FrontlineUtils.getLogger(ClickatellInternetService.class);
private ClickatellHTTPGateway gateway;
private boolean connected;
private Service service;
/**
* Initialises the clickatell http gateway.
*/
private void initGateway() {
gateway = new ClickatellHTTPGateway(getIdentifier(), getApiId(), getUsername(), getPassword());
gateway.setOutbound(true);
gateway.setSecure(isEncrypted());
service = new Service();
service.addGateway(gateway);
}
/** @throws ReceiveNotSupportedException always, as {@link ClickatellInternetService} does not support receiving. */
protected void receiveSms() throws ReceiveNotSupportedException {
throw new ReceiveNotSupportedException();
}
/**
* Send an SMS message using this phone handler.
* @param message The message to be sent.
*/
protected void sendSmsDirect(FrontlineMessage message) {
LOG.trace("ENTER");
LOG.debug(Library.getLibraryDescription());
LOG.debug("Version: " + Library.getLibraryVersion());
LOG.debug("Sending [" + message.getTextContent() + "] to [" + message.getRecipientMsisdn() + "]");
OutboundMessage oMessage;
// FIXME if we are sending a binary message, we should create one of those here instead
if(message.isBinaryMessage()) {
oMessage = new OutboundMessage(message.getRecipientMsisdn(), message.getBinaryContent());
} else {
oMessage = new OutboundMessage(message.getRecipientMsisdn(), message.getTextContent());
}
if(message.getRecipientSmsPort() > 0) {
oMessage.setDstPort(message.getRecipientSmsPort());
}
String fromMsisdn = getMsisdn();
if (fromMsisdn != null && !fromMsisdn.equals("")) {
oMessage.setFrom(fromMsisdn);
}
try {
service.sendMessage(oMessage);
if (oMessage.getMessageStatus() == MessageStatuses.SENT) {
message.setStatus(Status.SENT);
LOG.debug("Message [" + message + "] was sent!");
} else {
//message not sent
//failed to send
if (oMessage.getFailureCause() == FailureCauses.NO_CREDIT) {
setStatus(SmsInternetServiceStatus.LOW_CREDIT, Float.toString(gateway.queryBalance()));
creditLow();
}
message.setStatus(Status.FAILED);
LOG.debug("Message [" + message + "] was not sent. Cause: [" + oMessage.getFailureCause() + "]");
}
} catch(Exception ex) {
message.setStatus(Status.FAILED);
LOG.debug("Failed to send message [" + message + "]", ex);
LOG.info("Failed to send message");
} finally {
if (smsListener != null) {
smsListener.outgoingMessageEvent(this, message);
}
}
}
/**
* Starts the service. Normally we initialise the gateway.
*/
protected void init() throws SmsInternetServiceInitialisationException {
LOG.trace("ENTER");
initGateway();
try{
service.startService();
connected = true;
this.setStatus(SmsInternetServiceStatus.CONNECTED, null);
} catch (Throwable t) {
LOG.debug("Could not connect", t);
connected = false;
this.setStatus(SmsInternetServiceStatus.FAILED_TO_CONNECT, null);
throw new SmsInternetServiceInitialisationException(t);
}
LOG.trace("EXIT");
}
/**
* Stops the service showing the remaining credits
*/
public void creditLow() {
stopThisThing();
}
/**
* Forces the service to stop all gateways.
*/
protected void deinit() {
LOG.trace("ENTER");
try {
service.stopService();
} catch(Throwable t) {
// If anything goes wrong in the service stopping.
LOG.debug("Error stopping service", t);
}
connected = false;
this.setStatus(SmsInternetServiceStatus.DISCONNECTED, null);
LOG.trace("EXIT");
}
/**
* We do not currently support receive through Clickatell.
* @return <code>false</code>
*/
public boolean supportsReceive() {
return false;
}
/**
* 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() + " (API " + this.getApiId() + ")";
}
/**
* 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_API, "");
defaultSettings.put(PROPERTY_FROM_MSISDN, new PhoneSection(""));
defaultSettings.put(PROPERTY_SSL, Boolean.FALSE);
defaultSettings.put(PROPERTY_USE_FOR_SENDING, Boolean.TRUE);
return defaultSettings;
}
/**
* Check if this service is encrypted using SSL.
*/
public boolean isEncrypted() {
return getPropertyValue(PROPERTY_SSL, Boolean.class);
}
/**
* Set this device to be used for receiving messages.
*/
public void setUseForReceiving(boolean use) {
throw new ReceiveNotSupportedException();
}
/**
* Set this device to be used for sending SMS messages.
*/
public void setUseForSending(boolean use) {
setProperty(PROPERTY_USE_FOR_SENDING, new Boolean(use));
}
/**
* @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_API}
*/
private String getApiId() {
return getPropertyValue(PROPERTY_API, String.class);
}
/**
* Check if this device is being used to receive SMS messages.
* Our implementation of Clickatell does not support SMS receiving.
*/
public boolean isUseForReceiving() {
return false;
}
/**
* Checks if this device is being used to send SMS messages.
*/
public boolean isUseForSending() {
return getPropertyValue(PROPERTY_USE_FOR_SENDING, Boolean.class);
}
/**
* 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();
}
/**
* Checks if the service is currently connected.
* TODO could rename this isLive().
* @return {@link #connected}.
*/
public boolean isConnected() {
verifyGatewayConnection();
return this.connected;
}
public String getServiceName() {
return this.getUsername() + UI_NAME_SEPARATOR + SmsInternetServiceSettingsHandler.getProviderName(getClass());
}
/**
* Verifies if the gateway is running okay or we need to restart it.
*/
private void verifyGatewayConnection() {
// Let's verify it is really connected
if (this.gateway != null
&& gateway.getGatewayStatus() != GatewayStatuses.OK) {
try {
if (gateway.getStarted()) {
this.setStatus(SmsInternetServiceStatus.TRYING_TO_RECONNECT, null);
service.stopService();
connected = false;
service.startService();
connected = true;
this.setStatus(SmsInternetServiceStatus.CONNECTED, null);
}
} catch (Throwable t) {
// We could not reconnect
LOG.error("Error reconnecting", t);
connected = false;
}
}
}
/**
* 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;
}
public String getPort() {
return null;
}
public String getDisplayPort() {
// TODO Auto-generated method stub
return null;
}
}