Skip to content

Commit d91e403

Browse files
authored
Merge pull request #86 from thewalrusisben/feature/numbers-api
Numbers API Methods: Searching phone numbers available for purchase Purchasing a phone number Fetching all purchased phone numbers Fetching a purchased phone number Updating a purchased phone number Cancel a purchased phone number
2 parents 2134881 + a91829e commit d91e403

21 files changed

+1196
-6
lines changed

api/src/main/java/com/messagebird/MessageBirdClient.java

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
import com.messagebird.objects.MessageResponse;
2020
import com.messagebird.objects.MsgType;
2121
import com.messagebird.objects.PagedPaging;
22+
import com.messagebird.objects.PhoneNumbersLookup;
23+
import com.messagebird.objects.PhoneNumbersResponse;
24+
import com.messagebird.objects.PurchasedNumber;
25+
import com.messagebird.objects.PurchasedNumberCreatedResponse;
26+
import com.messagebird.objects.PurchasedNumbersResponse;
27+
import com.messagebird.objects.PurchasedNumbersFilter;
2228
import com.messagebird.objects.Verify;
2329
import com.messagebird.objects.VerifyRequest;
2430
import com.messagebird.objects.VoiceMessage;
@@ -54,9 +60,11 @@
5460
import java.nio.charset.StandardCharsets;
5561
import java.net.URLEncoder;
5662
import java.util.Arrays;
63+
import java.util.HashMap;
5764
import java.util.LinkedHashMap;
5865
import java.util.LinkedList;
5966
import java.util.List;
67+
import java.util.Locale;
6068
import java.util.Map;
6169

6270
/**
@@ -87,6 +95,7 @@ public class MessageBirdClient {
8795
private static final String BASE_URL_CONVERSATIONS_WHATSAPP_SANDBOX = "https://whatsapp-sandbox.messagebird.com/v1";
8896

8997
static final String VOICE_CALLS_BASE_URL = "https://voice.messagebird.com";
98+
static final String NUMBERS_CALLS_BASE_URL = "https://numbers.messagebird.com/v1";
9099
private static String[] supportedLanguages = {"de-DE", "en-AU", "en-UK", "en-US", "es-ES", "es-LA", "fr-FR", "it-IT", "nl-NL", "pt-BR"};
91100

92101
private static final String BALANCEPATH = "/balance";
@@ -1587,4 +1596,125 @@ private void verifyOffsetAndLimit(Integer offset, Integer limit) {
15871596
throw new IllegalArgumentException("Limit must be > 0");
15881597
}
15891598
}
1590-
}
1599+
1600+
/**
1601+
* Checks whether a particular country code is a recognized ISO Country.
1602+
*
1603+
* @param countryCode The country code in which the Number should be purchased.
1604+
* @throws IllegalArgumentException for invalid country code
1605+
*/
1606+
private void countryCodeIsValid(String countryCode) throws IllegalArgumentException {
1607+
final boolean isValid = Arrays.asList(Locale.getISOCountries()).contains(countryCode);
1608+
if (!isValid) {
1609+
throw new IllegalArgumentException("Invalid Country Code Provided.");
1610+
}
1611+
}
1612+
1613+
/**
1614+
* Lists Numbers that are available to purchase in a particular country code, without any filters.
1615+
*
1616+
* @param countryCode The country code in which the Number should be purchased.
1617+
* @throws GeneralException general exception
1618+
* @throws UnauthorizedException if client is unauthorized
1619+
* @throws NotFoundException if the resource is missing
1620+
* @throws IllegalArgumentException if the country code provided is invalid
1621+
*/
1622+
public PhoneNumbersResponse listNumbersForPurchase(String countryCode) throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException {
1623+
countryCodeIsValid(countryCode);
1624+
final String url = String.format("%s/available-phone-numbers", NUMBERS_CALLS_BASE_URL);
1625+
return messageBirdService.requestByID(url, countryCode, PhoneNumbersResponse.class);
1626+
}
1627+
1628+
/**
1629+
* Lists Numbers that are available to purchase in a particular country code, according to specified search criteria.
1630+
*
1631+
* @param countryCode The country code in which the Number should be purchased.
1632+
* @param params Parameters to filter the resulting phone numbers returned.
1633+
* @throws GeneralException general exception
1634+
* @throws UnauthorizedException if client is unauthorized
1635+
* @throws NotFoundException if the resource is missing
1636+
* @throws IllegalArgumentException if the country code provided is invalid
1637+
*/
1638+
public PhoneNumbersResponse listNumbersForPurchase(String countryCode, PhoneNumbersLookup params) throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException {
1639+
countryCodeIsValid(countryCode);
1640+
final String url = String.format("%s/available-phone-numbers", NUMBERS_CALLS_BASE_URL);
1641+
return messageBirdService.requestByID(url, countryCode, params.toHashMap(), PhoneNumbersResponse.class);
1642+
}
1643+
1644+
/**
1645+
* Purchases a phone number. To be used in conjunction with listNumbersForPurchase to identify available numbers.
1646+
*
1647+
* @param number The number to purchase.
1648+
* @param countryCode The country code in which the Number should be purchased.
1649+
* @throws GeneralException general exception
1650+
* @throws UnauthorizedException if client is unauthorized
1651+
* @throws IllegalArgumentException if the country code provided is invalid
1652+
*/
1653+
public PurchasedNumberCreatedResponse purchaseNumber(String number, String countryCode, int billingIntervalMonths) throws UnauthorizedException, GeneralException, IllegalArgumentException {
1654+
countryCodeIsValid(countryCode);
1655+
final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
1656+
final Map<String, Object> payload = new LinkedHashMap<String, Object>();
1657+
payload.put("number", number);
1658+
payload.put("countryCode", countryCode);
1659+
if (!Arrays.asList(1, 3, 6, 9).contains(billingIntervalMonths)) {
1660+
throw new IllegalArgumentException("Billing Interval Must Be Either 1, 3, 6, or 9.");
1661+
}
1662+
payload.put("billingIntervalMonths", billingIntervalMonths);
1663+
1664+
return messageBirdService.sendPayLoad(url, payload, PurchasedNumberCreatedResponse.class);
1665+
}
1666+
1667+
/**
1668+
* Lists Numbers that were purchased using the account credentials that the client was initialized with.
1669+
*
1670+
* @param filter Filters the list of purchased numbers according to search criteria.
1671+
* @throws UnauthorizedException if client is unauthorized
1672+
* @throws GeneralException general exception
1673+
* @throws NotFoundException if the resource is missing
1674+
*/
1675+
public PurchasedNumbersResponse listPurchasedNumbers(PurchasedNumbersFilter filter) throws UnauthorizedException, GeneralException, NotFoundException {
1676+
final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
1677+
return messageBirdService.requestByID(url, null, filter.toHashMap(), PurchasedNumbersResponse.class);
1678+
}
1679+
1680+
/**
1681+
* Returns a Number that has already been purchased on the initialized account.
1682+
*
1683+
* @param number The number whose data should be returned.
1684+
* @throws UnauthorizedException if client is unauthorized
1685+
* @throws GeneralException general exception
1686+
* @throws NotFoundException if the Number is missing
1687+
*/
1688+
public PurchasedNumber viewPurchasedNumber(String number) throws UnauthorizedException, GeneralException, NotFoundException {
1689+
final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
1690+
return messageBirdService.requestByID(url, number, PurchasedNumber.class);
1691+
}
1692+
1693+
/**
1694+
* Updates tags on a particular existing Number. Any number of parameters after the number can be given to apply multiple tags.
1695+
*
1696+
* @param number The number to update.
1697+
* @param tags A tag to apply to the number.
1698+
* @throws UnauthorizedException if client is unauthorized
1699+
* @throws GeneralException general exception
1700+
*/
1701+
public PurchasedNumber updateNumber(String number, String... tags) throws UnauthorizedException, GeneralException {
1702+
final String url = String.format("%s/phone-numbers/%s", NUMBERS_CALLS_BASE_URL, number);
1703+
final Map<String, List<String>> payload = new HashMap<String, List<String>>();
1704+
payload.put("tags", Arrays.asList(tags));
1705+
return messageBirdService.sendPayLoad("PATCH", url, payload, PurchasedNumber.class);
1706+
}
1707+
1708+
/**
1709+
* Cancels a particular number.
1710+
*
1711+
* @param number The number to cancel.
1712+
* @throws GeneralException general exception
1713+
* @throws UnauthorizedException if client is unauthorized
1714+
* @throws NotFoundException if the resource is missing
1715+
*/
1716+
public void cancelNumber(String number) throws UnauthorizedException, GeneralException, NotFoundException {
1717+
final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
1718+
messageBirdService.deleteByID(url, number);
1719+
}
1720+
}

api/src/main/java/com/messagebird/MessageBirdServiceImpl.java

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ public <T, P> T getJsonData(final String request, final P payload, final String
200200
if (!isURLAbsolute(url)) {
201201
url = serviceUrl + url;
202202
}
203-
204203
final APIResponse apiResponse = doRequest(requestType, url, payload);
205204

206205
final String body = apiResponse.getBody();
@@ -366,13 +365,13 @@ private synchronized static void allowPatchRequestsIfNeeded() throws GeneralExce
366365
Field modifiersField = Field.class.getDeclaredField("modifiers");
367366
modifiersField.setAccessible(true);
368367
modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL);
369-
368+
370369
Object noInstanceBecauseStaticField = null;
371-
370+
372371
// Determine what methods should be allowed.
373372
String[] existingMethods = (String[]) methodsField.get(noInstanceBecauseStaticField);
374373
String[] allowedMethods = getAllowedMethods(existingMethods);
375-
374+
376375
// Override the actual field to allow PATCH.
377376
methodsField.set(noInstanceBecauseStaticField, allowedMethods);
378377

@@ -618,6 +617,17 @@ private void saveClose(final InputStream is) {
618617
}
619618
}
620619

620+
/**
621+
* Encodes a key/value pair with percent encoding.
622+
*
623+
* @param key the key name to be used
624+
* @param value the value to be assigned to that key
625+
* @return String
626+
*/
627+
private String encodeKeyValuePair(String key, Object value) throws UnsupportedEncodingException {
628+
return URLEncoder.encode(key, String.valueOf(StandardCharsets.UTF_8)) + "=" + URLEncoder.encode(String.valueOf(value), String.valueOf(StandardCharsets.UTF_8));
629+
}
630+
621631
/**
622632
* Build a path variable for GET requests
623633
*
@@ -631,7 +641,30 @@ private String getPathVariables(final Map<String, Object> map) {
631641
bpath.append("&");
632642
}
633643
try {
634-
bpath.append(URLEncoder.encode(param.getKey(), String.valueOf(StandardCharsets.UTF_8))).append("=").append(URLEncoder.encode(String.valueOf(param.getValue()), String.valueOf(StandardCharsets.UTF_8)));
644+
// Check to see if the value is a Collection
645+
if (param.getValue() instanceof Collection) {
646+
// If it is, cast the value as a Collection explicitly
647+
// so it can be iterated over. Its values should be
648+
// appended to the querystring parameters using the
649+
// original key provided (e.g., ?features=sms&features=mms)
650+
Collection<?> col = (Collection<?>) param.getValue();
651+
Iterator<?> iterator = col.iterator();
652+
int count = 0;
653+
// While there are still remaining iterables
654+
while (iterator.hasNext()) {
655+
// Append & if not the first iterable
656+
if (count > 0) {
657+
bpath.append("&");
658+
}
659+
// Append the encoded querystring key/value pair.
660+
// the value is returned from the next() call
661+
bpath.append(encodeKeyValuePair(param.getKey(), iterator.next()));
662+
count++;
663+
}
664+
} else {
665+
// If the value is not a collection, create the querystring value directly.
666+
bpath.append(encodeKeyValuePair(param.getKey(), param.getValue()));
667+
}
635668
} catch (UnsupportedEncodingException exception) {
636669
// Do nothing
637670
}

api/src/main/java/com/messagebird/objects/MsgType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
public enum MsgType {
99
sms("sms"),
10+
mms("mms"),
1011
binary("binary"),
1112
premium("premium"),
1213
flash("flash");
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.messagebird.objects;
2+
3+
import com.messagebird.objects.PhoneNumberFeature;
4+
5+
import java.util.EnumSet;
6+
7+
public class PhoneNumber {
8+
private String number;
9+
private String country;
10+
private String region;
11+
private String locality;
12+
private EnumSet<PhoneNumberFeature> features;
13+
private String type;
14+
15+
public String getNumber() {
16+
return this.number;
17+
}
18+
19+
public String getCountry() {
20+
return this.country;
21+
}
22+
23+
public String getRegion() {
24+
return this.region;
25+
}
26+
27+
public String getLocality() {
28+
return this.locality;
29+
}
30+
31+
public EnumSet<PhoneNumberFeature> getFeatures() {
32+
return this.features;
33+
}
34+
35+
public String getType() {
36+
return this.type;
37+
}
38+
39+
@Override
40+
public String toString() {
41+
return "PhoneNumber{" +
42+
"number='" + number + "\'" +
43+
", country='" + country + "\'" +
44+
", region='" + region + "\'" +
45+
", locality='" + locality + "\'" +
46+
", features=" + features +
47+
", type='" + type + "\'" +
48+
"}";
49+
}
50+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.messagebird.objects;
2+
3+
public enum PhoneNumberFeature {
4+
5+
SMS("sms"),
6+
MMS("mms"),
7+
VOICE("voice");
8+
9+
private String type;
10+
11+
PhoneNumberFeature(String type) {
12+
this.type = type;
13+
}
14+
15+
@Override
16+
public String toString() {
17+
return this.type;
18+
}
19+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.messagebird.objects;
2+
3+
public enum PhoneNumberSearchPattern {
4+
START("start"),
5+
ANYWHERE("anywhere"),
6+
END("end");
7+
8+
private String type;
9+
10+
PhoneNumberSearchPattern(String type) {
11+
this.type = type;
12+
}
13+
14+
@Override
15+
public String toString() {
16+
return this.type;
17+
}
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.messagebird.objects;
2+
3+
public enum PhoneNumberType {
4+
LANDLINE("landline"),
5+
MOBILE("mobile"),
6+
PREMIUM_RATE("premium_rate");
7+
8+
private String type;
9+
10+
PhoneNumberType(String type) {
11+
this.type = type;
12+
}
13+
14+
@Override
15+
public String toString() {
16+
return this.type;
17+
}
18+
}

0 commit comments

Comments
 (0)