package esmska.transfer;
import esmska.data.CountryPrefix;
import esmska.utils.L10N;
import esmska.data.SMS;
import esmska.data.Tuple;
import esmska.utils.LogSupport;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
/** Class containing methods, which can be called from gateway scripts.
* For each gateway script a separate class should be created.
* @author ripper
*/
public class GatewayExecutor {
public static enum Problem {
/** Gateway script author provided his own message.
* Requires the message as a parameter (you can use HTML 3.2). */
CUSTOM_MESSAGE,
/** A fix for this gateway is being worked on.
* Requires URL for a webpage with more details as a parameter. */
FIX_IN_PROGRESS,
/** Gateway provided its own error message.
* Requires gateway message as a parameter (you can use HTML 3.2). */
GATEWAY_MESSAGE,
/** This is used for internal Esmska purposes. Don't use it from
inside gateway scripts.
Requires the message as a parameter (you can use HTML 3.2). */
INTERNAL_MESSAGE,
/** The user has not waited long enough to send another message
* or message quota has been reached. */
LIMIT_REACHED,
/** The message text was too long. */
LONG_TEXT,
/** The user does not have sufficient credit. */
NO_CREDIT,
/** The sending failed but gateway hasn't provided any reason for it. */
NO_REASON,
/** The sender signature was missing. */
SIGNATURE_NEEDED,
/** Message that unknown error happened, maybe error in the script.
* Make sure you set this problem before making any other HTTP requests
* (logging out, etc), because the last web content will get logged
* automatically right after you set this problem. */
UNKNOWN,
/** This gateway is for some reason currently unusable.
* Requires URL for a webpage with more details as a parameter. */
UNUSABLE,
/** The login or password was wrong. */
WRONG_AUTH,
/** The security code was wrong. */
WRONG_CODE,
/** The recepient number was wrong. */
WRONG_NUMBER,
/** The sender signature was wrong. */
WRONG_SIGNATURE,
}
private static final Problem[] needParams = new Problem[]{
Problem.CUSTOM_MESSAGE,
Problem.FIX_IN_PROGRESS,
Problem.GATEWAY_MESSAGE,
Problem.INTERNAL_MESSAGE,
Problem.UNUSABLE,
};
private static final ResourceBundle l10n = L10N.l10nBundle;
private static final Logger logger = Logger.getLogger(GatewayExecutor.class.getName());
/** Message saying how many free SMS are remaining. */
public static final String INFO_FREE_SMS_REMAINING =
l10n.getString("GatewayExecutor.INFO_FREE_SMS_REMAINING") + " ";
/** Message saying how much credit is remaining. */
public static final String INFO_CREDIT_REMAINING =
l10n.getString("GatewayExecutor.INFO_CREDIT_REMAINING") + " ";
/** Message used when gateway provides no info whether message was successfully sent or not. */
public static final String INFO_STATUS_NOT_PROVIDED =
l10n.getString("GatewayExecutor.INFO_STATUS_NOT_PROVIDED");
private final GatewayConnector connector = new GatewayConnector();
private final SMS sms;
private String referer;
private String lastTextContent;
public GatewayExecutor(SMS sms) {
this.sms = sms;
}
/** For description see {@link GatewayConnector#forgetCookie(
* java.lang.String, java.lang.String, java.lang.String)}
*/
public void forgetCookie(String name, String domain, String path) {
connector.forgetCookie(name, domain, path);
}
/** Make a GET request to a provided URL
* @param url base url where to connect, without any parameters or "?" at the end.
* In special cases when you don't use params, you can use url as a full url.
* But don't forget that parameters values must be url-encoded, which you can't
* do properly in JavaScript.
* @param params array of url params in form [key1,value1,key2,value2,...]
* @return content of the response. It may be String (when requesting HTML page) or
* just an array of bytes (when requesting eg. an image).
* @throws IOException when there is some problem in connecting
*/
public Object getURL(String url, String[] params) throws IOException {
try {
connector.setConnection(url, params, false, null);
connector.setReferer(referer);
boolean ok = connector.connect();
if (!ok) {
throw new IOException("Could not connect to URL");
}
if (connector.isTextContent()) {
lastTextContent = connector.getTextContent();
return connector.getTextContent();
} else {
// we don't log binary content
lastTextContent = null;
return connector.getBinaryContent();
}
} catch (IOException ex) {
logger.log(Level.WARNING, "Could not execute getURL", ex);
throw ex;
} catch (RuntimeException ex) {
logger.log(Level.WARNING, "Could not execute getURL", ex);
throw ex;
}
}
/** Make a POST request with specified data to a provided URL.
* @param url base url where to connect, without any parameters or "?" at the end.
* In special cases when you don't use params, you can use url as a full url.
* But don't forget that parameters values must be url-encoded, which you can't
* do properly in JavaScript.
* @param params array of url params in form [key1,value1,key2,value2,...]
* @param postData array of data to be sent in the request in form [key1,value1,key2,value2,...].
* This data will be properly url-encoded before sending.
* @return content of the response. It may be String (when requesting HTML page) or
* just an array of bytes (when requesting eg. an image).
* @throws IOException when there is some problem in connecting
*/
public Object postURL(String url, String[] params, String[] postData) throws IOException {
try {
connector.setConnection(url, params, true, postData);
connector.setReferer(referer);
boolean ok = connector.connect();
if (!ok) {
throw new IOException("Could not connect to URL");
}
if (connector.isTextContent()) {
lastTextContent = connector.getTextContent();
return connector.getTextContent();
} else {
// we don't log binary content
lastTextContent = null;
return connector.getBinaryContent();
}
} catch (IOException ex) {
logger.log(Level.WARNING, "Could not execute postURL", ex);
throw ex;
} catch (RuntimeException ex) {
logger.log(Level.WARNING, "Could not execute postURL", ex);
throw ex;
}
}
/** Ask user to recognize provided image code
* @param imageBytes image bytearray. Java must be able to display this image
* (PNG, GIF, JPEG, maybe something else).
* @param hint optional hint that can gateway say to user.
* @return Recognized image code. Never returns null, may return empty string.
*/
public String recognizeImage(byte[] imageBytes, String hint) throws InterruptedException,
InvocationTargetException, ExecutionException {
logger.fine("Resolving security code...");
if (imageBytes == null && StringUtils.isEmpty(hint)) {
return "";
}
ImageIcon image = imageBytes == null ? null : new ImageIcon(imageBytes);
sms.setImage(image);
sms.setImageHint(hint);
boolean resolved = ImageCodeManager.getResolver().resolveImageCode(sms);
if (!resolved) {
logger.info("Could not resolve security code or resolving cancelled");
}
return StringUtils.defaultString(sms.getImageCode());
}
/** Same as calling setProblem(problem, null). */
public void setProblem(Object problem) {
setProblem(problem, null);
}
/** Problem displayed when sending was unsuccessful.
* @param problem problem from Problem enum
* @param param some problems require additional string parameter, see their description
*/
public void setProblem(Object problem, String param) {
Problem prob;
if (problem instanceof String) {
prob = Problem.valueOf((String)problem);
} else {
prob = (Problem) problem;
}
//process additional params
if (ArrayUtils.contains(needParams, prob)) {
if (StringUtils.isEmpty(param)) {
throw new IllegalArgumentException("Missing additional parameter " +
"for provided problem " + prob);
}
}
// log if UNKNOWN (bad content or crash)
if (prob == Problem.UNKNOWN) {
logCrash();
}
sms.setProblem(new Tuple<Problem, String>(prob, param));
}
/** Optional supplemental message from gateway that is shown after message sending. */
public void setSupplementalMessage(String supplMessage) {
sms.setSupplMsg(supplMessage);
}
/** Referer (HTTP 'Referer' header) used for all following requests.
* Use null for resetting current value back to none.
*/
public void setReferer(String referer) {
this.referer = referer;
}
/** Pauses the execution for specified amount of time.
* Nothing happens if the amount is negative. */
public void sleep(long milliseconds) throws InterruptedException {
logger.log(Level.FINE, "Sleeping for {0} ms...", milliseconds);
if (milliseconds <= 0) {
return;
}
Thread.sleep(milliseconds);
}
/** Extract country prefix from phone number.
* @param phoneNumber Phone number in fully international format. May be null or
* incomplete.
* @return Country prefix if valid one is found in the number.
* Empty string otherwise.
*/
public String extractCountryPrefix(String phoneNumber) {
return StringUtils.defaultString(CountryPrefix.extractCountryPrefix(phoneNumber));
}
/** Set preferred language to retrieve web content.
* @param language two-letter language code as defined in ISO 639-1
*/
void setPreferredLanguage(String language) {
connector.setLanguage(language);
}
/** Log last webpage content preceding crash.
* Doesn't get logged twice if webpage debugging already enabled.
* @param content web page content, may be null
*/
private void logCrash() {
if (lastTextContent == null) {
//nothing to log
return;
}
Level level = LogSupport.getEsmskaLogger().getLevel();
if (level.equals(Level.ALL)) {
//this content was already logged
return;
}
LogSupport.getEsmskaLogger().setLevel(Level.ALL);
logger.log(Level.FINEST, "#### WEB CONTENT START ####\n{0}\n#### WEB CONTENT END ####", lastTextContent);
LogSupport.getEsmskaLogger().setLevel(level);
}
}