package io.bold;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
import com.google.common.io.Closeables;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.*;
import java.util.List;
/**
* Default implementation of {@link Client}.
*/
public class DefaultClient implements Client {
public static final int DEFAULT_TIMEOUT = 5000;
protected Proxy proxy;
protected String endpoint;
protected String username;
protected String password;
protected int timeoutMs;
public DefaultClient(String endpoint, String username, String password) {
this(endpoint, username, password, DEFAULT_TIMEOUT);
}
public DefaultClient(String endpoint, String username, String password, int timeoutMs) {
this(Proxy.NO_PROXY, endpoint, username, password, timeoutMs);
}
public DefaultClient(Proxy proxy, String endpoint, String username, String password, int timeoutMs) {
this.proxy = proxy;
this.endpoint = createApiEndPoint(endpoint);
this.username = username;
this.password = password;
this.timeoutMs = timeoutMs;
}
@Override
public List<Subscription> findSubscriptions(String number) throws IOException {
try {
return findSubscriptions(PhoneNumberUtil.getInstance().parse(number, null));
} catch (NumberParseException e) {
throw new BoldException(e);
}
}
@Override
public List<Subscription> findSubscriptions(Phonenumber.PhoneNumber number) throws IOException {
String phoneNr = PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.E164);
phoneNr = phoneNr.substring(1);
HttpURLConnection connection = openConnection("contacts/%s/subscriptions/", phoneNr);
connection.connect();
int statusCode = connection.getResponseCode();
InputStream response = connection.getInputStream();
try {
if (statusCode < 200 || statusCode >= 300) {
throw new BoldException("Encountered status " + statusCode);
}
ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, Subscription.class);
return mapper.readValue(response, type);
} finally {
Closeables.close(response, true);
}
}
@Override
public boolean subscribe(String number, String subscriptionName) throws IOException {
try {
return subscribe(PhoneNumberUtil.getInstance().parse(number, null), subscriptionName);
} catch (NumberParseException e) {
throw new BoldException(e);
}
}
@Override
public boolean subscribe(Phonenumber.PhoneNumber number, String subscriptionName) throws IOException {
String phoneNr = PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.E164);
phoneNr = phoneNr.substring(1);
HttpURLConnection connection = openConnection("contacts/%s/subscriptions/", phoneNr);
connection.setRequestMethod("POST");
connection.setDoOutput(true);
ObjectMapper mapper = new ObjectMapper();
OutputStream out = connection.getOutputStream();
mapper.writeValue(out, new Subscription(subscriptionName));
out.close();
connection.connect();
int statusCode = connection.getResponseCode();
if (statusCode == 200) {
// Already subscribed
return false;
}
if (statusCode == 201) {
return true;
}
throw new BoldException("Error subscribing " + number + " to " + subscriptionName);
}
@Override
public boolean unsubscribe(String number, String subscriptionName) throws IOException {
try {
return unsubscribe(PhoneNumberUtil.getInstance().parse(number, null), subscriptionName);
} catch (NumberParseException e) {
throw new BoldException(e);
}
}
@Override
public boolean unsubscribe(Phonenumber.PhoneNumber number, String subscriptionName) throws IOException {
String phoneNr = PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.E164);
phoneNr = phoneNr.substring(1);
String name = URLEncoder.encode(subscriptionName.trim().toUpperCase(), Charsets.UTF_8.name());
HttpURLConnection connection = openConnection("contacts/%s/subscriptions/%s/", phoneNr, name);
connection.setRequestMethod("DELETE");
connection.connect();
int statusCode = connection.getResponseCode();
if (statusCode == 204) {
return true;
}
if (statusCode == 404) {
// No such contact or subscription for the contact
return false;
}
throw new BoldException("Error unsubscribing " + number + " from " + subscriptionName);
}
@Override
public OutboundSms send(String hubId, String number, String content, String requiredSubscriptionName) throws IOException {
try {
Phonenumber.PhoneNumber nr = PhoneNumberUtil.getInstance().parse(number, null);
return send(hubId, nr, content, requiredSubscriptionName);
} catch (NumberParseException e) {
throw new BoldException(e);
}
}
@Override
public OutboundSms send(String hubId, Phonenumber.PhoneNumber number, String content, String requiredSubscriptionName) throws IOException {
String phoneNr = PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.E164);
SendSms sms = new SendSms();
sms.content = content;
sms.destinationNr = phoneNr;
sms.hubId = hubId;
sms.requiredSubscription = requiredSubscriptionName;
return send(sms);
}
@Override
public OutboundSms send(String hubId, long contactId, String content, String requiredSubscriptionName) throws IOException {
SendSms sms = new SendSms();
sms.content = content;
sms.destinationId = contactId;
sms.hubId = hubId;
sms.requiredSubscription = requiredSubscriptionName;
return send(sms);
}
@Override
public OutboundSms reply(long messageId, String content) throws IOException {
HttpURLConnection connection = openConnection("messages/in/%d/reply/", messageId);
connection.setRequestMethod("POST");
connection.setDoOutput(true);
ObjectMapper mapper = new ObjectMapper();
OutputStream out = connection.getOutputStream();
mapper.writeValue(out, new Reply(content));
out.close();
connection.connect();
int statusCode = connection.getResponseCode();
if (statusCode == 404) {
// Original message not found
return null;
}
if (statusCode == 201) {
InputStream response = connection.getInputStream();
try {
return mapper.readValue(response, OutboundSms.class);
} finally {
Closeables.close(response, true);
}
}
throw new BoldException("Error sending message");
}
protected OutboundSms send(SendSms sms) throws IOException {
HttpURLConnection connection = openConnection("messages/out/");
connection.setRequestMethod("POST");
connection.setDoOutput(true);
ObjectMapper mapper = new ObjectMapper();
OutputStream out = connection.getOutputStream();
mapper.writeValue(out, sms);
out.close();
connection.connect();
int statusCode = connection.getResponseCode();
if (statusCode == 200) {
// Contact cannot receive message based on criteria (blocked or no matching subscription if supplied)
return null;
}
if (statusCode == 201) {
InputStream response = connection.getInputStream();
try {
return mapper.readValue(response, OutboundSms.class);
} finally {
Closeables.close(response, true);
}
}
throw new BoldException("Error sending message");
}
protected HttpURLConnection openConnection(String fmt, Object... args) throws IOException {
URL url = createUrl(fmt, args);
String auth = BaseEncoding.base64Url().encode((username + ":" + password).getBytes());
HttpURLConnection connection = (HttpURLConnection) (proxy == null ? url.openConnection() : url.openConnection(proxy));
connection.setRequestProperty("Authorization", "Basic " + auth);
connection.setRequestProperty("User-Agent", "Bold.io Java Client");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
return connection;
}
protected URL createUrl(String fmt, Object[] args) throws IOException {
try {
return new URL(String.format(endpoint + fmt, args));
} catch (MalformedURLException e) {
throw new IOException(e);
}
}
public static String createApiEndPoint(String base) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(base), "An endpoint is required");
base = base.trim();
return base + (base.endsWith("/") ? "" : "/") + "api/";
}
public class SendSms {
@JsonProperty("hub_id")
public String hubId;
@JsonProperty("destination_nr")
public String destinationNr;
@JsonProperty("destination_id")
public Long destinationId;
public String content;
@JsonProperty("required_subscription")
public String requiredSubscription;
}
public class Reply {
public String content;
public Reply() {
super();
}
public Reply(String content) {
this.content = content;
}
}
}