- * I am placing this code in the Public Domain. Do with it as you will.
- * This software comes with no guarantees or warranties but with
- * plenty of well-wishing instead!
- * Please visit http://iharder.net/base64
- * periodically to check for updates or to contribute improvements.
- *
- *
- * @author Robert Harder
- * @author rob@iharder.net
- * @version 2.3.7
- */
-class Base64 {
-
-/* ******** P U B L I C F I E L D S ******** */
-
-
- /** No options specified. Value is zero. */
- public final static int NO_OPTIONS = 0;
-
- /** Specify that gzipped data should not be automatically gunzipped. */
- public final static int DONT_GUNZIP = 4;
-
- /**
- * Encode using Base64-like encoding that is URL- and Filename-safe as described
- * in Section 4 of RFC3548:
- * http://www.faqs.org/rfcs/rfc3548.html.
- * It is important to note that data encoded this way is not officially valid Base64,
- * or at the very least should not be called Base64 without also specifying that is
- * was encoded using the URL- and Filename-safe dialect.
- */
- public final static int URL_SAFE = 16;
-
-
- /**
- * Encode using the special "ordered" dialect of Base64 described here:
- * http://www.faqs.org/qa/rfcc-1940.html.
- */
- public final static int ORDERED = 32;
-
-
-/* ******** P R I V A T E F I E L D S ******** */
-
-
- /** The equals sign (=) as a byte. */
- private final static byte EQUALS_SIGN = (byte)'=';
-
-
- /** Preferred encoding. */
- private final static String PREFERRED_ENCODING = "US-ASCII";
-
-
- private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
- private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
-
-
-/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */
-
- /**
- * Translates a Base64 value to either its 6-bit reconstruction value
- * or a negative number indicating some other meaning.
- **/
- private final static byte[] _STANDARD_DECODABET = {
- -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
- -5,-5, // Whitespace: Tab and Linefeed
- -9,-9, // Decimal 11 - 12
- -5, // Whitespace: Carriage Return
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
- -9,-9,-9,-9,-9, // Decimal 27 - 31
- -5, // Whitespace: Space
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
- 62, // Plus sign at decimal 43
- -9,-9,-9, // Decimal 44 - 46
- 63, // Slash at decimal 47
- 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine
- -9,-9,-9, // Decimal 58 - 60
- -1, // Equals sign at decimal 61
- -9,-9,-9, // Decimal 62 - 64
- 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N'
- 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z'
- -9,-9,-9,-9,-9,-9, // Decimal 91 - 96
- 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm'
- 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z'
- -9,-9,-9,-9,-9 // Decimal 123 - 127
- ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
- };
-
-
-/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */
-
- /**
- * Used in decoding URL- and Filename-safe dialects of Base64.
- */
- private final static byte[] _URL_SAFE_DECODABET = {
- -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
- -5,-5, // Whitespace: Tab and Linefeed
- -9,-9, // Decimal 11 - 12
- -5, // Whitespace: Carriage Return
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
- -9,-9,-9,-9,-9, // Decimal 27 - 31
- -5, // Whitespace: Space
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
- -9, // Plus sign at decimal 43
- -9, // Decimal 44
- 62, // Minus sign at decimal 45
- -9, // Decimal 46
- -9, // Slash at decimal 47
- 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine
- -9,-9,-9, // Decimal 58 - 60
- -1, // Equals sign at decimal 61
- -9,-9,-9, // Decimal 62 - 64
- 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N'
- 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z'
- -9,-9,-9,-9, // Decimal 91 - 94
- 63, // Underscore at decimal 95
- -9, // Decimal 96
- 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm'
- 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z'
- -9,-9,-9,-9,-9 // Decimal 123 - 127
- ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
- };
-
-
-
-/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */
-
- /**
- * Used in decoding the "ordered" dialect of Base64.
- */
- private final static byte[] _ORDERED_DECODABET = {
- -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
- -5,-5, // Whitespace: Tab and Linefeed
- -9,-9, // Decimal 11 - 12
- -5, // Whitespace: Carriage Return
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
- -9,-9,-9,-9,-9, // Decimal 27 - 31
- -5, // Whitespace: Space
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
- -9, // Plus sign at decimal 43
- -9, // Decimal 44
- 0, // Minus sign at decimal 45
- -9, // Decimal 46
- -9, // Slash at decimal 47
- 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine
- -9,-9,-9, // Decimal 58 - 60
- -1, // Equals sign at decimal 61
- -9,-9,-9, // Decimal 62 - 64
- 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M'
- 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z'
- -9,-9,-9,-9, // Decimal 91 - 94
- 37, // Underscore at decimal 95
- -9, // Decimal 96
- 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm'
- 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z'
- -9,-9,-9,-9,-9 // Decimal 123 - 127
- ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
- };
-
-
-/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */
-
- /**
- * Returns one of the _SOMETHING_DECODABET byte arrays depending on
- * the options specified.
- * It's possible, though silly, to specify ORDERED and URL_SAFE
- * in which case one of them will be picked, though there is
- * no guarantee as to which one will be picked.
- */
- private final static byte[] getDecodabet( int options ) {
- if( (options & URL_SAFE) == URL_SAFE) {
- return _URL_SAFE_DECODABET;
- } else if ((options & ORDERED) == ORDERED) {
- return _ORDERED_DECODABET;
- } else {
- return _STANDARD_DECODABET;
- }
- } // end getAlphabet
-
-
-
- /** Defeats instantiation. */
- private Base64(){}
-
-
-
-/* ******** D E C O D I N G M E T H O D S ******** */
-
-
- /**
- * Decodes four bytes from array source
- * and writes the resulting bytes (up to three of them)
- * to destination.
- * The source and destination arrays can be manipulated
- * anywhere along their length by specifying
- * srcOffset and destOffset.
- * This method does not check to make sure your arrays
- * are large enough to accomodate srcOffset + 4 for
- * the source array or destOffset + 3 for
- * the destination array.
- * This method returns the actual number of bytes that
- * were converted from the Base64 encoding.
- *
This is the lowest level of the decoding methods with
- * all possible parameters.
- *
- *
- * @param source the array to convert
- * @param srcOffset the index where conversion begins
- * @param destination the array to hold the conversion
- * @param destOffset the index where output will be put
- * @param options alphabet type is pulled from this (standard, url-safe, ordered)
- * @return the number of decoded bytes converted
- * @throws NullPointerException if source or destination arrays are null
- * @throws IllegalArgumentException if srcOffset or destOffset are invalid
- * or there is not enough room in the array.
- * @since 1.3
- */
- private static int decode4to3(
- byte[] source, int srcOffset,
- byte[] destination, int destOffset, int options ) {
-
- // Lots of error checking and exception throwing
- if( source == null ){
- throw new NullPointerException( "Source array was null." );
- } // end if
- if( destination == null ){
- throw new NullPointerException( "Destination array was null." );
- } // end if
- if( srcOffset < 0 || srcOffset + 3 >= source.length ){
- throw new IllegalArgumentException( String.format(
- "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) );
- } // end if
- if( destOffset < 0 || destOffset +2 >= destination.length ){
- throw new IllegalArgumentException( String.format(
- "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) );
- } // end if
-
-
- byte[] DECODABET = getDecodabet( options );
-
- // Example: Dk==
- if( source[ srcOffset + 2] == EQUALS_SIGN ) {
- // Two ways to do the same thing. Don't know which way I like best.
- //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
- // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
- int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
- | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
-
- destination[ destOffset ] = (byte)( outBuff >>> 16 );
- return 1;
- }
-
- // Example: DkL=
- else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) {
- // Two ways to do the same thing. Don't know which way I like best.
- //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
- // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
- // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
- int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
- | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
- | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 );
-
- destination[ destOffset ] = (byte)( outBuff >>> 16 );
- destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 );
- return 2;
- }
-
- // Example: DkLE
- else {
- // Two ways to do the same thing. Don't know which way I like best.
- //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
- // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
- // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
- // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
- int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
- | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
- | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6)
- | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) );
-
-
- destination[ destOffset ] = (byte)( outBuff >> 16 );
- destination[ destOffset + 1 ] = (byte)( outBuff >> 8 );
- destination[ destOffset + 2 ] = (byte)( outBuff );
-
- return 3;
- }
- } // end decodeToBytes
-
-
-
-
- /**
- * Low-level access to decoding ASCII characters in
- * the form of a byte array. Ignores GUNZIP option, if
- * it's set. This is not generally a recommended method,
- * although it is used internally as part of the decoding process.
- * Special case: if len = 0, an empty array is returned. Still,
- * if you need more speed and reduced memory footprint (and aren't
- * gzipping), consider this method.
- *
- * @param source The Base64 encoded data
- * @param off The offset of where to begin decoding
- * @param len The length of characters to decode
- * @param options Can specify options such as alphabet type to use
- * @return decoded data
- * @throws java.io.IOException If bogus characters exist in source data
- * @since 1.3
- */
- public static byte[] decode( byte[] source, int off, int len, int options )
- throws java.io.IOException {
-
- // Lots of error checking and exception throwing
- if( source == null ){
- throw new NullPointerException( "Cannot decode null source array." );
- } // end if
- if( off < 0 || off + len > source.length ){
- throw new IllegalArgumentException( String.format(
- "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) );
- } // end if
-
- if( len == 0 ){
- return new byte[0];
- }else if( len < 4 ){
- throw new IllegalArgumentException(
- "Base64-encoded string must have at least four characters, but length specified was " + len );
- } // end if
-
- byte[] DECODABET = getDecodabet( options );
-
- int len34 = len * 3 / 4; // Estimate on array size
- byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
- int outBuffPosn = 0; // Keep track of where we're writing
-
- byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space
- int b4Posn = 0; // Keep track of four byte input buffer
- int i = 0; // Source array counter
- byte sbiDecode = 0; // Special value from DECODABET
-
- for( i = off; i < off+len; i++ ) { // Loop through source
-
- sbiDecode = DECODABET[ source[i]&0xFF ];
-
- // White space, Equals sign, or legit Base64 character
- // Note the values such as -5 and -9 in the
- // DECODABETs at the top of the file.
- if( sbiDecode >= WHITE_SPACE_ENC ) {
- if( sbiDecode >= EQUALS_SIGN_ENC ) {
- b4[ b4Posn++ ] = source[i]; // Save non-whitespace
- if( b4Posn > 3 ) { // Time to decode?
- outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options );
- b4Posn = 0;
-
- // If that was the equals sign, break out of 'for' loop
- if( source[i] == EQUALS_SIGN ) {
- break;
- } // end if: equals sign
- } // end if: quartet built
- } // end if: equals sign or better
- } // end if: white space, equals sign or better
- else {
- // There's a bad input character in the Base64 stream.
- throw new java.io.IOException( String.format(
- "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) );
- } // end else:
- } // each input character
-
- byte[] out = new byte[ outBuffPosn ];
- System.arraycopy( outBuff, 0, out, 0, outBuffPosn );
- return out;
- } // end decode
-
-
-
-
- /**
- * Decodes data from Base64 notation, automatically
- * detecting gzip-compressed data and decompressing it.
- *
- * @param s the string to decode
- * @return the decoded data
- * @throws java.io.IOException If there is a problem
- * @since 1.4
- */
- public static byte[] decode( String s ) throws java.io.IOException {
- return decode( s, NO_OPTIONS );
- }
-
-
-
- /**
- * Decodes data from Base64 notation, automatically
- * detecting gzip-compressed data and decompressing it.
- *
- * @param s the string to decode
- * @param options encode options such as URL_SAFE
- * @return the decoded data
- * @throws java.io.IOException if there is an error
- * @throws NullPointerException if s is null
- * @since 1.4
- */
- public static byte[] decode( String s, int options ) throws java.io.IOException {
-
- if( s == null ){
- throw new NullPointerException( "Input string was null." );
- } // end if
-
- byte[] bytes;
- try {
- bytes = s.getBytes( PREFERRED_ENCODING );
- } // end try
- catch( java.io.UnsupportedEncodingException uee ) {
- bytes = s.getBytes();
- } // end catch
- //
-
- // Decode
- bytes = decode( bytes, 0, bytes.length, options );
-
- // Check to see if it's gzip-compressed
- // GZIP Magic Two-Byte Number: 0x8b1f (35615)
- boolean dontGunzip = (options & DONT_GUNZIP) != 0;
- if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) {
-
- int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
- if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) {
- java.io.ByteArrayInputStream bais = null;
- java.util.zip.GZIPInputStream gzis = null;
- java.io.ByteArrayOutputStream baos = null;
- byte[] buffer = new byte[2048];
- int length = 0;
-
- try {
- baos = new java.io.ByteArrayOutputStream();
- bais = new java.io.ByteArrayInputStream( bytes );
- gzis = new java.util.zip.GZIPInputStream( bais );
-
- while( ( length = gzis.read( buffer ) ) >= 0 ) {
- baos.write(buffer,0,length);
- } // end while: reading input
-
- // No error? Get new bytes.
- bytes = baos.toByteArray();
-
- } // end try
- catch( java.io.IOException e ) {
- e.printStackTrace();
- // Just return originally-decoded bytes
- } // end catch
- finally {
- try{ baos.close(); } catch( Exception e ){}
- try{ gzis.close(); } catch( Exception e ){}
- try{ bais.close(); } catch( Exception e ){}
- } // end finally
-
- } // end if: gzipped
- } // end if: bytes.length >= 2
-
- return bytes;
- } // end decode
-
-} // end class Base64
diff --git a/api/src/main/java/com/messagebird/MessageBirdClient.java b/api/src/main/java/com/messagebird/MessageBirdClient.java
index 8d71bae2..583dcb40 100644
--- a/api/src/main/java/com/messagebird/MessageBirdClient.java
+++ b/api/src/main/java/com/messagebird/MessageBirdClient.java
@@ -3,38 +3,24 @@
import com.messagebird.exceptions.GeneralException;
import com.messagebird.exceptions.NotFoundException;
import com.messagebird.exceptions.UnauthorizedException;
-import com.messagebird.objects.Balance;
-import com.messagebird.objects.Contact;
-import com.messagebird.objects.ContactList;
-import com.messagebird.objects.ContactRequest;
-import com.messagebird.objects.ErrorReport;
-import com.messagebird.objects.Group;
-import com.messagebird.objects.GroupList;
-import com.messagebird.objects.GroupRequest;
-import com.messagebird.objects.Hlr;
-import com.messagebird.objects.Lookup;
-import com.messagebird.objects.LookupHlr;
-import com.messagebird.objects.Message;
-import com.messagebird.objects.MessageList;
-import com.messagebird.objects.MessageResponse;
-import com.messagebird.objects.MsgType;
-import com.messagebird.objects.PagedPaging;
-import com.messagebird.objects.Verify;
-import com.messagebird.objects.VerifyRequest;
-import com.messagebird.objects.VoiceMessage;
-import com.messagebird.objects.VoiceMessageList;
-import com.messagebird.objects.VoiceMessageResponse;
+import com.messagebird.objects.*;
import com.messagebird.objects.conversations.Conversation;
import com.messagebird.objects.conversations.ConversationList;
import com.messagebird.objects.conversations.ConversationMessage;
import com.messagebird.objects.conversations.ConversationMessageList;
import com.messagebird.objects.conversations.ConversationMessageRequest;
+import com.messagebird.objects.conversations.ConversationSendRequest;
+import com.messagebird.objects.conversations.ConversationSendResponse;
import com.messagebird.objects.conversations.ConversationStartRequest;
import com.messagebird.objects.conversations.ConversationStatus;
+import com.messagebird.objects.conversations.ConversationUpdateRequest;
import com.messagebird.objects.conversations.ConversationWebhook;
import com.messagebird.objects.conversations.ConversationWebhookCreateRequest;
import com.messagebird.objects.conversations.ConversationWebhookList;
import com.messagebird.objects.conversations.ConversationWebhookUpdateRequest;
+import com.messagebird.objects.integrations.Template;
+import com.messagebird.objects.integrations.TemplateList;
+import com.messagebird.objects.integrations.TemplateResponse;
import com.messagebird.objects.voicecalls.RecordingResponse;
import com.messagebird.objects.voicecalls.TranscriptionResponse;
import com.messagebird.objects.voicecalls.VoiceCall;
@@ -48,16 +34,19 @@
import com.messagebird.objects.voicecalls.Webhook;
import com.messagebird.objects.voicecalls.WebhookList;
import com.messagebird.objects.voicecalls.WebhookResponseData;
-
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.Set;
/**
* Message bird general client
@@ -83,11 +72,14 @@ public class MessageBirdClient {
* can, however, override this behaviour by providing absolute URLs
* ourselves.
*/
- private static final String BASE_URL_CONVERSATIONS = "https://conversations.messagebird.com/v1";
- private static final String BASE_URL_CONVERSATIONS_WHATSAPP_SANDBOX = "https://whatsapp-sandbox.messagebird.com/v1";
-
+ static final String CONVERSATIONS_BASE_URL = "https://conversations.messagebird.com/v1";
static final String VOICE_CALLS_BASE_URL = "https://voice.messagebird.com";
+ static final String NUMBERS_CALLS_BASE_URL = "https://numbers.messagebird.com/v1";
+ static final String MESSAGING_BASE_URL = "https://messaging.messagebird.com/v1";
+ static final String INTEGRATIONS_BASE_URL_V2 = "https://integrations.messagebird.com/v2";
+ static final String INTEGRATIONS_BASE_URL_V3 = "https://integrations.messagebird.com/v3";
private static String[] supportedLanguages = {"de-DE", "en-AU", "en-UK", "en-US", "es-ES", "es-LA", "fr-FR", "it-IT", "nl-NL", "pt-BR"};
+ static final String PARTNER_ACCOUNTS_BASE_URL = "https://partner-accounts.messagebird.com";
private static final String BALANCEPATH = "/balance";
private static final String CONTACTPATH = "/contacts";
@@ -97,10 +89,13 @@ public class MessageBirdClient {
private static final String LOOKUPPATH = "/lookup";
private static final String MESSAGESPATH = "/messages";
private static final String VERIFYPATH = "/verify";
+ private static final String VERIFYEMAILPATH = "/verify/messages/email";
private static final String VOICEMESSAGESPATH = "/voicemessages";
- private static final String CONVERSATION_PATH = "/conversations";
- private static final String CONVERSATION_MESSAGE_PATH = "/messages";
- private static final String CONVERSATION_WEBHOOK_PATH = "/webhooks";
+ static final String CONVERSATION_PATH = "/conversations";
+ static final String CONVERSATION_SEND_PATH = "/send";
+ static final String CONVERSATION_MESSAGE_PATH = "/messages";
+ static final String CONVERSATION_WEBHOOK_PATH = "/webhooks";
+ static final String INTEGRATIONS_WHATSAPP_PATH = "/platforms/whatsapp";
static final String VOICECALLSPATH = "/calls";
static final String LEGSPATH = "/legs";
static final String RECORDINGPATH = "/recordings";
@@ -108,34 +103,34 @@ public class MessageBirdClient {
static final String WEBHOOKS = "/webhooks";
static final String VOICECALLFLOWPATH = "/call-flows";
private static final String VOICELEGS_SUFFIX_PATH = "/legs";
+ static final String FILES_PATH = "/files";
+ static final String TEMPLATES_PATH = "/templates";
+ static final String UNPAUSE_TEMAPLATE_PATH = "/unpause";
+ static final String OUTBOUND_SMS_PRICING_PATH = "/pricing/sms/outbound";
+ static final String OUTBOUND_SMS_PRICING_SMPP_PATH = "/pricing/sms/outbound/smpp/%s";
+
static final String RECORDING_DOWNLOAD_FORMAT = ".wav";
-
+
static final String TRANSCRIPTION_DOWNLOAD_FORMAT = ".txt";
private static final int DEFAULT_MACHINE_TIMEOUT_VALUE = 7000;
private static final int MIN_MACHINE_TIMEOUT_VALUE = 400;
private static final int MAX_MACHINE_TIMEOUT_VALUE = 10000;
+ private static final String[] MESSAGE_LIST_FILTERS_VALS = {"originator", "recipient", "direction", "searchterm", "type", "contact_id", "status", "from", "until"};
+ private static final Set MESSAGE_LIST_FILTERS = new HashSet<>(Arrays.asList(MESSAGE_LIST_FILTERS_VALS));
+
+ private static final String[] CONVERSATION_MESSAGE_LIST_FILTERS_VALS = {"ids", "from"};
+ private static final Set CONVERSATION_MESSAGE_LIST_FILTERS = new HashSet<>(Arrays.asList(CONVERSATION_MESSAGE_LIST_FILTERS_VALS));
+
private final String DOWNLOADS = "Downloads";
private MessageBirdService messageBirdService;
- private String conversationsBaseUrl;
-
- public enum Feature {
- ENABLE_CONVERSATION_API_WHATSAPP_SANDBOX
- }
public MessageBirdClient(final MessageBirdService messageBirdService) {
this.messageBirdService = messageBirdService;
- this.conversationsBaseUrl = BASE_URL_CONVERSATIONS;
}
- public MessageBirdClient(final MessageBirdService messageBirdService, List features) {
- this(messageBirdService);
- if(features.indexOf(Feature.ENABLE_CONVERSATION_API_WHATSAPP_SANDBOX) >= 0) {
- this.conversationsBaseUrl = BASE_URL_CONVERSATIONS_WHATSAPP_SANDBOX;
- }
- }
/****************************************************************************************************/
/** Balance and HRL methods **/
/****************************************************************************************************/
@@ -275,6 +270,18 @@ public MessageList listMessages(final Integer offset, final Integer limit) throw
return messageBirdService.requestList(MESSAGESPATH, offset, limit, MessageList.class);
}
+ public MessageList listMessagesFiltered(final Integer offset, final Integer limit, final Map filters) throws UnauthorizedException, GeneralException {
+ verifyOffsetAndLimit(offset, limit);
+
+ for (String filter : filters.keySet()) {
+ if (!MESSAGE_LIST_FILTERS.contains(filter)) {
+ throw new IllegalArgumentException("Invalid filter name: " + filter);
+ }
+ }
+
+ return messageBirdService.requestList(MESSAGESPATH, filters, offset, limit, MessageList.class);
+ }
+
/**
* Delete a message from the Messagebird server
*
@@ -466,13 +473,27 @@ public Verify verifyToken(String id, String token) throws NotFoundException, Gen
* @throws UnauthorizedException if client is unauthorized
* @throws GeneralException general exception
*/
- Verify getVerifyObject(String id) throws NotFoundException, GeneralException, UnauthorizedException {
+ public Verify getVerifyObject(String id) throws NotFoundException, GeneralException, UnauthorizedException {
if (id == null || id.isEmpty()) {
throw new IllegalArgumentException("ID cannot be empty for verify");
}
return messageBirdService.requestByID(VERIFYPATH, id, Verify.class);
}
+ /**
+ * @param messageId is for the email message part of a verify object
+ * @return Verify object
+ * @throws NotFoundException if id is not found
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ */
+ public VerifyMessage getVerifyEmailMessage(String messageId) throws UnauthorizedException, GeneralException, NotFoundException {
+ if (messageId == null || messageId.isEmpty()) {
+ throw new IllegalArgumentException("ID cannot be empty for verify email message");
+ }
+ return messageBirdService.requestByID(VERIFYEMAILPATH, messageId, VerifyMessage.class);
+ }
+
/**
* @param id id for deleting verify object
* @throws NotFoundException if id is not found
@@ -662,8 +683,8 @@ public VoiceCallFlowResponse sendVoiceCallFlow(final VoiceCallFlowRequest voiceC
* @param id String
* @param voiceCallFlowRequest VoiceCallFlowRequest
* @return VoiceCallFlowResponse
- * @throws UnauthorizedException
- * @throws GeneralException
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
*/
public VoiceCallFlowResponse updateVoiceCallFlow(String id, VoiceCallFlowRequest voiceCallFlowRequest)
throws UnauthorizedException, GeneralException {
@@ -868,7 +889,7 @@ public Conversation viewConversation(final String id) throws NotFoundException,
if (id == null) {
throw new IllegalArgumentException("Id must be specified");
}
- String url = this.conversationsBaseUrl + CONVERSATION_PATH;
+ String url = CONVERSATIONS_BASE_URL + CONVERSATION_PATH;
return messageBirdService.requestByID(url, id, Conversation.class);
}
@@ -884,8 +905,12 @@ public Conversation updateConversation(final String id, final ConversationStatus
if (id == null) {
throw new IllegalArgumentException("Id must be specified.");
}
- String url = String.format("%s%s/%s", this.conversationsBaseUrl, CONVERSATION_PATH, id);
- return messageBirdService.sendPayLoad("PATCH", url, status, Conversation.class);
+ if (status == null) {
+ throw new IllegalArgumentException("An updated conversation status must be specified");
+ }
+ ConversationUpdateRequest payload = new ConversationUpdateRequest(status);
+ String url = String.format("%s%s/%s", CONVERSATIONS_BASE_URL, CONVERSATION_PATH, id);
+ return messageBirdService.sendPayLoad("PATCH", url, payload, Conversation.class);
}
/**
@@ -897,7 +922,7 @@ public Conversation updateConversation(final String id, final ConversationStatus
*/
public ConversationList listConversations(final int offset, final int limit)
throws UnauthorizedException, GeneralException {
- String url = this.conversationsBaseUrl + CONVERSATION_PATH;
+ String url = CONVERSATIONS_BASE_URL + CONVERSATION_PATH;
return messageBirdService.requestList(url, offset, limit, ConversationList.class);
}
@@ -921,10 +946,22 @@ public ConversationList listConversations() throws UnauthorizedException, Genera
*/
public Conversation startConversation(ConversationStartRequest request)
throws UnauthorizedException, GeneralException {
- String url = String.format("%s%s/start", this.conversationsBaseUrl, CONVERSATION_PATH);
+ String url = String.format("%s%s/start", CONVERSATIONS_BASE_URL, CONVERSATION_PATH);
return messageBirdService.sendPayLoad(url, request, Conversation.class);
}
+ /**
+ * sendMessage allows you to send message to users over any communication platform supported by Programmable Conversations
+ *
+ * @param request Data for this request.
+ * @return The created Message in ConversationSendResponse object.
+ */
+ public ConversationSendResponse sendMessage(ConversationSendRequest request)
+ throws UnauthorizedException, GeneralException {
+ String url = String.format("%s%s", CONVERSATIONS_BASE_URL, CONVERSATION_SEND_PATH);
+ return messageBirdService.sendPayLoad(url, request, ConversationSendResponse.class);
+ }
+
/**
* Gets a ConversationMessage listing with specified pagination options.
*
@@ -940,7 +977,7 @@ public ConversationMessageList listConversationMessages(
) throws UnauthorizedException, GeneralException {
String url = String.format(
"%s%s/%s%s",
- this.conversationsBaseUrl,
+ CONVERSATIONS_BASE_URL,
CONVERSATION_PATH,
conversationId,
CONVERSATION_MESSAGE_PATH
@@ -971,10 +1008,27 @@ public ConversationMessageList listConversationMessages(
*/
public ConversationMessage viewConversationMessage(final String messageId)
throws NotFoundException, GeneralException, UnauthorizedException {
- String url = this.conversationsBaseUrl + CONVERSATION_MESSAGE_PATH;
+ String url = CONVERSATIONS_BASE_URL + CONVERSATION_MESSAGE_PATH;
return messageBirdService.requestByID(url, messageId, ConversationMessage.class);
}
+ /**
+ * Gets conversation messages based on query param.
+ *
+ * @param queryParams only `ids` and `from` is available as an option
+ * @return The retrieved messages.
+ */
+ public ConversationMessageList listConversationMessagesWithQueryParam(Map queryParams)
+ throws NotFoundException, GeneralException, UnauthorizedException {
+ for (String queryParam : queryParams.keySet()) {
+ if (!CONVERSATION_MESSAGE_LIST_FILTERS.contains(queryParam)) {
+ throw new IllegalArgumentException("Invalid filter name: " + queryParam);
+ }
+ }
+ String url = CONVERSATIONS_BASE_URL + CONVERSATION_MESSAGE_PATH;
+ return messageBirdService.requestByID(url, null, queryParams, ConversationMessageList.class);
+ }
+
/**
* Sends a message to an existing Conversation.
*
@@ -988,7 +1042,7 @@ public ConversationMessage sendConversationMessage(
) throws UnauthorizedException, GeneralException {
String url = String.format(
"%s%s/%s%s",
- this.conversationsBaseUrl,
+ CONVERSATIONS_BASE_URL,
CONVERSATION_PATH,
conversationId,
CONVERSATION_MESSAGE_PATH
@@ -1003,7 +1057,7 @@ public ConversationMessage sendConversationMessage(
*/
public void deleteConversationWebhook(final String webhookId)
throws NotFoundException, GeneralException, UnauthorizedException {
- String url = this.conversationsBaseUrl + CONVERSATION_WEBHOOK_PATH;
+ String url = CONVERSATIONS_BASE_URL + CONVERSATION_WEBHOOK_PATH;
messageBirdService.deleteByID(url, webhookId);
}
@@ -1015,7 +1069,7 @@ public void deleteConversationWebhook(final String webhookId)
*/
public ConversationWebhook sendConversationWebhook(final ConversationWebhookCreateRequest request)
throws UnauthorizedException, GeneralException {
- String url = this.conversationsBaseUrl + CONVERSATION_WEBHOOK_PATH;
+ String url = CONVERSATIONS_BASE_URL + CONVERSATION_WEBHOOK_PATH;
return messageBirdService.sendPayLoad(url, request, ConversationWebhook.class);
}
@@ -1030,7 +1084,7 @@ public ConversationWebhook updateConversationWebhook(final String id, final Conv
throw new IllegalArgumentException("Conversation webhook ID must be specified.");
}
- String url = this.conversationsBaseUrl + CONVERSATION_WEBHOOK_PATH + "/" + id;
+ String url = CONVERSATIONS_BASE_URL + CONVERSATION_WEBHOOK_PATH + "/" + id;
return messageBirdService.sendPayLoad("PATCH", url, request, ConversationWebhook.class);
}
@@ -1041,7 +1095,7 @@ public ConversationWebhook updateConversationWebhook(final String id, final Conv
* @return The retrieved webhook.
*/
public ConversationWebhook viewConversationWebhook(final String webhookId) throws NotFoundException, GeneralException, UnauthorizedException {
- String url = this.conversationsBaseUrl + CONVERSATION_WEBHOOK_PATH;
+ String url = CONVERSATIONS_BASE_URL + CONVERSATION_WEBHOOK_PATH;
return messageBirdService.requestByID(url, webhookId, ConversationWebhook.class);
}
@@ -1054,7 +1108,7 @@ public ConversationWebhook viewConversationWebhook(final String webhookId) throw
*/
public ConversationWebhookList listConversationWebhooks(final int offset, final int limit)
throws UnauthorizedException, GeneralException {
- String url = this.conversationsBaseUrl + CONVERSATION_WEBHOOK_PATH;
+ String url = CONVERSATIONS_BASE_URL + CONVERSATION_WEBHOOK_PATH;
return messageBirdService.requestList(url, offset, limit, ConversationWebhookList.class);
}
@@ -1319,6 +1373,38 @@ public RecordingResponse listRecordings(String callID, String legId, final Integ
return messageBirdService.requestList(url, offset, limit, RecordingResponse.class);
}
+ /**
+ * Deletes a voice recording
+ *
+ * @param callID Voice call ID
+ * @param legID Leg ID
+ * @param recordingID Recording ID
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ */
+ public void deleteRecording(final String callID, final String legID, final String recordingID)
+ throws NotFoundException, GeneralException, UnauthorizedException {
+ if (callID == null) {
+ throw new IllegalArgumentException("Call ID must be specified.");
+ }
+ if (legID == null) {
+ throw new IllegalArgumentException("Leg ID must be specified.");
+ }
+ if (recordingID == null) {
+ throw new IllegalArgumentException("Recording ID must be specified.");
+ }
+ String url = String.format(
+ "%s%s/%s%s/%s%s",
+ VOICE_CALLS_BASE_URL,
+ VOICECALLSPATH,
+ callID,
+ LEGSPATH,
+ legID,
+ RECORDINGPATH
+ );
+ messageBirdService.deleteByID(url, recordingID);
+ }
+
/**
* Function to view recording by call id , leg id and recording id
*
@@ -1587,4 +1673,615 @@ private void verifyOffsetAndLimit(Integer offset, Integer limit) {
throw new IllegalArgumentException("Limit must be > 0");
}
}
-}
\ No newline at end of file
+
+ /**
+ * Checks whether a particular country code is a recognized ISO Country.
+ *
+ * @param countryCode The country code in which the Number should be purchased.
+ * @throws IllegalArgumentException for invalid country code
+ */
+ private void countryCodeIsValid(String countryCode) throws IllegalArgumentException {
+ final boolean isValid = Arrays.asList(Locale.getISOCountries()).contains(countryCode);
+ if (!isValid) {
+ throw new IllegalArgumentException("Invalid Country Code Provided.");
+ }
+ }
+
+ /**
+ * Lists Numbers that are available to purchase in a particular country code, without any filters.
+ *
+ * @param countryCode The country code in which the Number should be purchased.
+ * @throws GeneralException general exception
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws NotFoundException if the resource is missing
+ * @throws IllegalArgumentException if the country code provided is invalid
+ */
+ public PhoneNumbersResponse listNumbersForPurchase(String countryCode) throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException {
+ countryCodeIsValid(countryCode);
+ final String url = String.format("%s/available-phone-numbers", NUMBERS_CALLS_BASE_URL);
+ return messageBirdService.requestByID(url, countryCode, PhoneNumbersResponse.class);
+ }
+
+ /**
+ * Lists Numbers that are available to purchase in a particular country code, according to specified search criteria.
+ *
+ * @param countryCode The country code in which the Number should be purchased.
+ * @param params Parameters to filter the resulting phone numbers returned.
+ * @throws GeneralException general exception
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws NotFoundException if the resource is missing
+ * @throws IllegalArgumentException if the country code provided is invalid
+ */
+ public PhoneNumbersResponse listNumbersForPurchase(String countryCode, PhoneNumbersLookup params) throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException {
+ countryCodeIsValid(countryCode);
+ final String url = String.format("%s/available-phone-numbers", NUMBERS_CALLS_BASE_URL);
+ return messageBirdService.requestByID(url, countryCode, params.toHashMap(), PhoneNumbersResponse.class);
+ }
+
+ /**
+ * Purchases a phone number. To be used in conjunction with listNumbersForPurchase to identify available numbers.
+ *
+ * @param number The number to purchase.
+ * @param countryCode The country code in which the Number should be purchased.
+ * @throws GeneralException general exception
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws IllegalArgumentException if the country code provided is invalid
+ */
+ public PurchasedNumberCreatedResponse purchaseNumber(String number, String countryCode, int billingIntervalMonths) throws UnauthorizedException, GeneralException, IllegalArgumentException {
+ countryCodeIsValid(countryCode);
+ final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
+ final Map payload = new LinkedHashMap();
+ payload.put("number", number);
+ payload.put("countryCode", countryCode);
+ if (!Arrays.asList(1, 3, 6, 9).contains(billingIntervalMonths)) {
+ throw new IllegalArgumentException("Billing Interval Must Be Either 1, 3, 6, or 9.");
+ }
+ payload.put("billingIntervalMonths", billingIntervalMonths);
+
+ return messageBirdService.sendPayLoad(url, payload, PurchasedNumberCreatedResponse.class);
+ }
+
+ /**
+ * Lists Numbers that were purchased using the account credentials that the client was initialized with.
+ *
+ * @param filter Filters the list of purchased numbers according to search criteria.
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if the resource is missing
+ */
+ public PurchasedNumbersResponse listPurchasedNumbers(PurchasedNumbersFilter filter) throws UnauthorizedException, GeneralException, NotFoundException {
+ final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
+ return messageBirdService.requestByID(url, null, filter.toHashMap(), PurchasedNumbersResponse.class);
+ }
+
+ /**
+ * Returns a Number that has already been purchased on the initialized account.
+ *
+ * @param number The number whose data should be returned.
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if the Number is missing
+ */
+ public PurchasedNumber viewPurchasedNumber(String number) throws UnauthorizedException, GeneralException, NotFoundException {
+ final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
+ return messageBirdService.requestByID(url, number, PurchasedNumber.class);
+ }
+
+ /**
+ * Updates tags on a particular existing Number. Any number of parameters after the number can be given to apply multiple tags.
+ *
+ * @param number The number to update.
+ * @param tags A tag to apply to the number.
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ */
+ public PurchasedNumber updateNumber(String number, String... tags) throws UnauthorizedException, GeneralException {
+ final String url = String.format("%s/phone-numbers/%s", NUMBERS_CALLS_BASE_URL, number);
+ final Map> payload = new HashMap>();
+ payload.put("tags", Arrays.asList(tags));
+ return messageBirdService.sendPayLoad("PATCH", url, payload, PurchasedNumber.class);
+ }
+
+ /**
+ * Cancels a particular number.
+ *
+ * @param number The number to cancel.
+ * @throws GeneralException general exception
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws NotFoundException if the resource is missing
+ */
+ public void cancelNumber(String number) throws UnauthorizedException, GeneralException, NotFoundException {
+ final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
+ messageBirdService.deleteByID(url, number);
+ }
+
+ /**
+ * Uploads a file and returns the assigned ID.
+ *
+ * @param binary the bytes of the file to upload.
+ * @param contentType the content type of the file (e.g. "image/png").
+ * @param filename optional filename to set in the upload request headers.
+ * @return FileUploadResponse
+ * @throws GeneralException general exception
+ * @throws UnauthorizedException if client is unauthorized
+ * @see #downloadFile
+ */
+ public FileUploadResponse uploadFile(byte[] binary, String contentType, String filename) throws GeneralException, UnauthorizedException {
+ if (binary == null) {
+ throw new IllegalArgumentException("File binary must be specified.");
+ }
+ if (contentType == null) {
+ throw new IllegalArgumentException("Content type must be specified.");
+ }
+
+ final String url = MESSAGING_BASE_URL + FILES_PATH;
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", contentType);
+ if (filename != null) {
+ headers.put("filename", filename);
+ }
+ return messageBirdService.sendPayLoad("POST", url, headers, binary, FileUploadResponse.class);
+ }
+
+ /**
+ * Downloads a file and stores it with the provided filename in the basePath directory. The
+ * basePath may be null. If basePath is null, the default download directory will be the
+ * /Download folder in the user home directory. The filename may be null. If filename is null,
+ * the provided id will be used as the filename instead.
+ *
+ * @param id the ID of the file, provided when the file was uploaded
+ * @param basePath store location. It should be a directory. Property is nullable if $HOME is accessible
+ * @param filename the name of the file to download to.
+ * @return the path where the downloaded file is stored
+ * @throws NotFoundException if the file does not exist
+ * @throws GeneralException general exception
+ * @throws UnauthorizedException if client is unauthorized
+ * @see #uploadFile
+ */
+ public String downloadFile(String id, String filename, String basePath) throws GeneralException, UnauthorizedException, NotFoundException {
+ if (id == null) {
+ throw new IllegalArgumentException("File ID must be specified.");
+ }
+
+ if (filename == null) {
+ filename = id;
+ }
+
+ final String url = String.format("%s%s/%s", MESSAGING_BASE_URL, FILES_PATH, id);
+ return messageBirdService.getBinaryData(url, basePath, filename);
+ }
+
+ /****************************************************************************************************/
+ /** WhatsApp Templates **/
+ /****************************************************************************************************/
+
+ /**
+ * Create a WhatsApp message template through messagebird.
+ *
+ * @param template {@link Template} object to be created
+ * @return {@link TemplateResponse} response object
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws IllegalArgumentException invalid template format
+ */
+ public TemplateResponse createWhatsAppTemplate(final Template template)
+ throws UnauthorizedException, GeneralException, IllegalArgumentException {
+ template.validate();
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+ return messageBirdService.sendPayLoad(url, template, TemplateResponse.class);
+ }
+
+ /**
+ * Update a WhatsApp message template through MessageBird.
+ *
+ * @param template {@link Template} object to be created
+ * @param templateName A name as returned by getWhatsAppTemplateBy in the name variable
+ * @param language A language code as returned by getWhatsAppTemplateBy in the language variable
+ * @return {@link TemplateResponse} response object
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws IllegalArgumentException invalid template format
+ */
+ public TemplateResponse updateWhatsAppTemplate(final Template template, final String templateName, final String language)
+ throws UnauthorizedException, GeneralException, IllegalArgumentException {
+ template.validate();
+
+ String url = String.format(
+ "%s%s%s/%s/%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ templateName,
+ language);
+
+ return messageBirdService.sendPayLoad("PUT",url, template, TemplateResponse.class);
+ }
+ /**
+ * Gets a WhatsAppTemplate listing with specified pagination options.
+ *
+ * @param offset Number of objects to skip.
+ * @param limit Number of objects to take.
+ * @return List of templates.
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ */
+ public TemplateList listWhatsAppTemplates(final int offset, final int limit)
+ throws UnauthorizedException, GeneralException {
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V3,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+ return messageBirdService.requestList(url, offset, limit, TemplateList.class);
+ }
+
+ /**
+ * Gets a WhatsAppTemplate listing with specified pagination options and a wabaID or channelID filter.
+ *
+ * @param offset Number of objects to skip.
+ * @param limit Number of objects to take.
+ * @param wabaID The WABA ID to filter templates by.
+ * @param channelID A channel ID filter to return only templates that can be sent via that channel.
+ * @return List of templates.
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws IllegalArgumentException if the provided arguments are not valid
+ */
+ public TemplateList listWhatsAppTemplates(final int offset, final int limit, final String wabaID, final String channelID)
+ throws UnauthorizedException, GeneralException, IllegalArgumentException {
+ validateWABAIDAndChannelIDArguments(wabaID, channelID);
+
+ Map map = new LinkedHashMap<>();
+ if (wabaID != null) map.put("wabaId", wabaID);
+ if (channelID != null) map.put("channelId", channelID);
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V3,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+ return messageBirdService.requestList(url, map, offset, limit, TemplateList.class);
+ }
+
+ /**
+ * Gets a template listing with default pagination options.
+ *
+ * @return List of whatsapp templates.
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ */
+ public TemplateList listWhatsAppTemplates() throws UnauthorizedException, GeneralException {
+ final int offset = 0;
+ final int limit = 10;
+
+ return listWhatsAppTemplates(offset, limit);
+ }
+
+ /**
+ * Retrieves the template of an existing template name.
+ *
+ * @param templateName A name as returned by getWhatsAppTemplateBy in the name variable
+ * @return {@code List} template list
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if template name is not found
+ * @throws IllegalArgumentException if the provided arguments are not valid
+ */
+ public List getWhatsAppTemplatesBy(final String templateName)
+ throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException {
+ if (templateName == null) {
+ throw new IllegalArgumentException("Template name must be specified.");
+ }
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+
+ return messageBirdService.requestByIdAsList(url, templateName, TemplateResponse.class);
+ }
+
+ /**
+ * Retrieves the template of an existing template name.
+ *
+ * @param templateName A name as returned by getWhatsAppTemplateBy in the name variable
+ * @param wabaID An optional WABA ID to look for the template ID under.
+ * @param channelID An optional channel ID to specify. If the template can be sent via the channel, it will return the template.
+ *
+ * @return {@code List} template list
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if template name is not found under the given WABA or cannot be sent under the supplied channel ID
+ * @throws IllegalArgumentException if the provided arguments are not valid
+ */
+ public List getWhatsAppTemplatesBy(final String templateName, final String wabaID, final String channelID)
+ throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException {
+ if (templateName == null) {
+ throw new IllegalArgumentException("Template name must be specified.");
+ }
+
+ String id = String.format("%s%s", templateName, getWabaIDOrChannelIDQuery(wabaID, channelID));
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+
+ return messageBirdService.requestByIdAsList(url, id, TemplateResponse.class);
+ }
+
+ /**
+ * Retrieves the template of an existing template name and language under the first waba connected to the requesting user.
+ *
+ * @param templateName A name as returned by getWhatsAppTemplateBy in the name variable
+ * @param language A language code as returned by getWhatsAppTemplateBy in the language variable
+ *
+ * @return {@code TemplateResponse} template
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if template name and language are not found under the first waba connected to the requesting user.
+ * @throws IllegalArgumentException if the provided arguments are not valid
+ */
+ public TemplateResponse fetchWhatsAppTemplateBy(final String templateName, final String language)
+ throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException {
+ if (templateName == null || language == null) {
+ throw new IllegalArgumentException("Template name and language must be specified.");
+ }
+
+ String url = String.format(
+ "%s%s%s/%s/%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ templateName,
+ language
+ );
+ return messageBirdService.request(url, TemplateResponse.class);
+ }
+
+ /**
+ * Retrieves the template of an existing template name and language under a WABA or for a channel.
+ *
+ * @param templateName A name as returned by getWhatsAppTemplateBy in the name variable
+ * @param language A language code as returned by getWhatsAppTemplateBy in the language variable
+ * @param wabaID An optional WABA ID to look for the template ID under.
+ * @param channelID An optional channel ID to specify. If the template can be sent via the channel, it will return the template.
+ *
+ * @return {@code TemplateResponse} template
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if template name and language are not found under the given WABA or cannot be sent under the supplied channel ID.
+ * @throws IllegalArgumentException if the provided arguments are not valid
+ */
+ public TemplateResponse fetchWhatsAppTemplateBy(final String templateName, final String language, final String wabaID, final String channelID)
+ throws GeneralException, UnauthorizedException, NotFoundException, IllegalArgumentException {
+ if (templateName == null || language == null) {
+ throw new IllegalArgumentException("Template name and language must be specified.");
+ }
+
+ String url = String.format(
+ "%s%s%s/%s/%s%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ templateName,
+ language,
+ getWabaIDOrChannelIDQuery(wabaID, channelID)
+ );
+ return messageBirdService.request(url, TemplateResponse.class);
+ }
+
+ /**
+ * Validates the WABA ID and Channel ID argument pair.
+ *
+ * @param wabaID A WABA ID.
+ * @param channelID A channel ID.
+ * @throws IllegalArgumentException if the argument pair is invalid.
+ */
+ private void validateWABAIDAndChannelIDArguments(String wabaID, String channelID)
+ throws IllegalArgumentException {
+ if (wabaID == null && channelID == null) {
+ throw new IllegalArgumentException("wabaID or channelID must be specified");
+ }
+
+ if (wabaID != null && channelID != null) {
+ throw new IllegalArgumentException("only supply wabaID or channelID - not both");
+ }
+ }
+
+ /**
+ * Validates the WABA ID and Channel ID argument pair and returns a valid query parameter string.
+ *
+ * @param wabaID A WABA ID.
+ * @param channelID A channel ID.
+ * @throws IllegalArgumentException if the argument pair is invalid.
+ */
+ private String getWabaIDOrChannelIDQuery(String wabaID, String channelID)
+ throws IllegalArgumentException {
+ validateWABAIDAndChannelIDArguments(wabaID, channelID);
+
+ String query = "";
+
+ if (wabaID != null) {
+ query = String.format("?wabaId=%s", wabaID);
+ }
+ if (channelID != null) {
+ query = String.format("?channelId=%s", channelID);
+ }
+
+ return query;
+ }
+
+ /**
+ * Delete templates of an existing template name.
+ *
+ * @param templateName A template name which is created on the MessageBird platform
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if template name is not found
+ */
+ public void deleteTemplatesBy(final String templateName)
+ throws UnauthorizedException, GeneralException, NotFoundException {
+ if (templateName == null) {
+ throw new IllegalArgumentException("Template name must be specified.");
+ }
+
+ String url = String.format(
+ "%s%s%s/%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ templateName
+ );
+ messageBirdService.delete(url, null);
+ }
+
+ public void unpauseTemplatesByTemplateName(final String templateName)
+ throws UnauthorizedException, GeneralException {
+ if (templateName == null) {
+ throw new IllegalArgumentException("Template name must be specified.");
+ }
+
+ String url = String.format(
+ "%s%s%s%s/%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ UNPAUSE_TEMAPLATE_PATH,
+ templateName
+ );
+ messageBirdService.sendPayLoad("POST", url, "", null);
+ }
+
+ /**
+ * Function to create a child account
+ *
+ * @param childAccountRequest of child account to create
+ * @return ChildAccountResponse created
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ */
+ public ChildAccountCreateResponse createChildAccount(final ChildAccountRequest childAccountRequest) throws UnauthorizedException, GeneralException {
+ if (childAccountRequest.getName() == null || childAccountRequest.getName().isEmpty()) {
+ throw new IllegalArgumentException("Name must be specified.");
+ }
+
+ String url = String.format("%s%s", PARTNER_ACCOUNTS_BASE_URL, "/child-accounts");
+ return messageBirdService.sendPayLoad(url, childAccountRequest, ChildAccountCreateResponse.class);
+ }
+
+ /**
+ * Function to update a child account
+ *
+ * @param id of child account to update
+ * @return ChildAccountResponse created
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ */
+ public ChildAccountResponse updateChildAccount(final String name, final String id) throws UnauthorizedException, GeneralException {
+ if (name == null) {
+ throw new IllegalArgumentException("Name must be specified.");
+ }
+
+ if (id == null) {
+ throw new IllegalArgumentException("Child account id must be specified.");
+ }
+ final ChildAccountRequest childAccountRequest = new ChildAccountRequest();
+ childAccountRequest.setName(name);
+ final String url = String.format("%s/child-accounts/%s", PARTNER_ACCOUNTS_BASE_URL, id);
+ return messageBirdService.sendPayLoad("PATCH", url, childAccountRequest, ChildAccountResponse.class);
+ }
+
+ /**
+ * Function to get a child account
+ *
+ * @param id of child account to update
+ * @return ChildAccountResponse created
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if id is not found
+ */
+ public ChildAccountDetailedResponse getChildAccountById(final String id) throws UnauthorizedException, GeneralException, NotFoundException {
+ if (id == null) {
+ throw new IllegalArgumentException("Child account id must be specified.");
+ }
+ return messageBirdService.requestByID(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", id, ChildAccountDetailedResponse.class);
+ }
+
+ /**
+ * Function to get a child account
+ *
+ * @return ChildAccountResponse created
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ */
+ public List getChildAccounts(final Integer offset, final Integer limit) throws UnauthorizedException, GeneralException {
+ verifyOffsetAndLimit(offset, limit);
+ return messageBirdService.requestList(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", offset, limit, List.class);
+ }
+
+ /**
+ * Function to delete a child account
+ *
+ * @param id of child account to delete
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if id is not found
+ */
+ public void deleteChildAccount(final String id) throws UnauthorizedException, GeneralException, NotFoundException {
+ if (id == null) {
+ throw new IllegalArgumentException("Child account id must be specified.");
+ }
+
+ String url = String.format("%s/child-accounts", PARTNER_ACCOUNTS_BASE_URL);
+ System.out.println("url: " + url);
+ messageBirdService.deleteByID(url, id);
+ }
+
+ /**
+ * Returns outbound pricing for the default SMS configuration for the authenticated account.
+ *
+ * @return outbound pricing for the default SMS configuration for the authenticated account
+ *
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if pricing information could not be found
+ *
+ * @see Pricing API
+ */
+ public OutboundSmsPriceResponse getOutboundSmsPrices() throws GeneralException, UnauthorizedException, NotFoundException {
+ return messageBirdService.request(OUTBOUND_SMS_PRICING_PATH, OutboundSmsPriceResponse.class);
+ }
+
+ /**
+ * Returns outbound SMS pricing for a specific SMPP username.
+ *
+ * @param smppUsername the SMPP SystemID provided by MessageBird
+ *
+ * @return outbound SMS pricing for the given SMPP username
+ *
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if pricing information could not be found for the given SMPP username
+ *
+ * @see Pricing API
+ */
+ public OutboundSmsPriceResponse getOutboundSmsPrices(final String smppUsername) throws GeneralException, UnauthorizedException, NotFoundException {
+ if (smppUsername == null) {
+ throw new IllegalArgumentException("SMPP username must be specified.");
+ }
+
+ final String url = String.format(OUTBOUND_SMS_PRICING_SMPP_PATH, smppUsername);
+ return messageBirdService.request(url, OutboundSmsPriceResponse.class);
+ }
+}
diff --git a/api/src/main/java/com/messagebird/MessageBirdService.java b/api/src/main/java/com/messagebird/MessageBirdService.java
index c100da10..43201fb7 100644
--- a/api/src/main/java/com/messagebird/MessageBirdService.java
+++ b/api/src/main/java/com/messagebird/MessageBirdService.java
@@ -1,16 +1,30 @@
package com.messagebird;
-import java.util.Map;
-
import com.messagebird.exceptions.GeneralException;
import com.messagebird.exceptions.NotFoundException;
import com.messagebird.exceptions.UnauthorizedException;
import com.messagebird.objects.PagedPaging;
+import java.util.List;
+import java.util.Map;
/**
* Created by rvt on 1/7/15.
*/
public interface MessageBirdService {
+
+ /**
+ * Send GET request . It will retrieve a json object R back.
+ *
+ * @author ssk910
+ * @param request path to the request, for example "/messages/id/language"
+ * @param clazz Class type to return
+ * @return new class with returned dataset
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if id not found
+ */
+ R request(String request, Class clazz) throws UnauthorizedException, GeneralException, NotFoundException;
+
/**
* Execute a object by ID request. It will add the id to the request parameter and retreive a json object R back.
*
@@ -25,6 +39,20 @@ public interface MessageBirdService {
R requestByID(String request, String id, Map params, Class clazz) throws UnauthorizedException, GeneralException, NotFoundException;
+ /**
+ * Execute a object by ID request. It will add the id to the request parameter and retrieve a list of an object E back.
+ *
+ * @author ssk910
+ * @param request path to the request, for example "/messages"
+ * @param id id of the object to request. id can be null in case request's that don't need a id, for example /balance
+ * @param elementClass Class type of List to return
+ * @return new list of elementClass
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException
+ */
+ List requestByIdAsList(String request, String id, Class elementClass) throws UnauthorizedException, GeneralException, NotFoundException;
+
/**
* Delete a object by ID.
*
@@ -36,6 +64,19 @@ public interface MessageBirdService {
*/
void deleteByID(String request, String id) throws UnauthorizedException, GeneralException, NotFoundException;
+ /**
+ * Send DELETE request. It will retrieve a json object R back.
+ *
+ * @author ssk910
+ * @param request path to the request, for example "/messages/id/language"
+ * @param clazz Class type to return
+ * @return class with returned dataset
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ * @throws NotFoundException if id not found
+ */
+ R delete(String request, Class clazz) throws UnauthorizedException, GeneralException, NotFoundException;
+
/**
* Request a List 'of' object.
* Allow to request a listMessage or listViewMessages objects.
@@ -51,6 +92,22 @@ public interface MessageBirdService {
*/
R requestList(String request, Integer offset, Integer limit, Class clazz) throws UnauthorizedException, GeneralException;
+ /**
+ * Request a List 'of' object.
+ * Allow to request a listMessage or listViewMessages objects.
+ * @see com.messagebird.objects.MessageList
+ *
+ * @param request request from client
+ * @param params additional query params
+ * @param offset offset of data to return
+ * @param limit limit number of objects, incase you notice you pass in '1' a lot, please consider using requestByID if you know the ID of the message
+ * @param clazz object type to return
+ * @return base class
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ */
+ R requestList(String request, Map params, Integer offset, Integer limit, Class clazz) throws UnauthorizedException, GeneralException;
+
/**
* Request a List 'of' object.
* Allow to request a listMessage or listViewMessages objects.
@@ -91,6 +148,20 @@ public interface MessageBirdService {
*/
R sendPayLoad(String method, String request, P payload, Class clazz) throws UnauthorizedException, GeneralException;
+ /**
+ * Send a payload with the provided method and headers and receive a payload object.
+ *
+ * @param method HTTP method to use for the request
+ * @param request path to the request, for example "/messages"
+ * @param headers additional headers to set on the request
+ * @param payload payload to send to the server
+ * @param clazz object type to return
+ * @return base class
+ * @throws UnauthorizedException if client is unauthorized
+ * @throws GeneralException general exception
+ */
+ R sendPayLoad(String method, String request, Map headers, P payload, Class clazz) throws UnauthorizedException, GeneralException;
+
/**
* Gets the data from the request URL and stores it to basePath/fileName
*
diff --git a/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java b/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java
index a45a2279..ef179b45 100644
--- a/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java
+++ b/api/src/main/java/com/messagebird/MessageBirdServiceImpl.java
@@ -1,6 +1,20 @@
package com.messagebird;
-import java.io.*;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.*;
+import com.messagebird.exceptions.GeneralException;
+import com.messagebird.exceptions.NotFoundException;
+import com.messagebird.exceptions.UnauthorizedException;
+import com.messagebird.objects.ErrorReport;
+import com.messagebird.objects.PagedPaging;
+import org.apache.maven.artifact.versioning.ComparableVersion;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.HttpURLConnection;
@@ -10,18 +24,16 @@
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
-import java.util.*;
-
-import com.fasterxml.jackson.annotation.JsonInclude.Include;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.messagebird.exceptions.GeneralException;
-import com.messagebird.exceptions.NotFoundException;
-import com.messagebird.exceptions.UnauthorizedException;
-import com.messagebird.objects.ErrorReport;
-import com.messagebird.objects.PagedPaging;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
/**
* Implementation of MessageBirdService
@@ -50,8 +62,7 @@ public class MessageBirdServiceImpl implements MessageBirdService {
private static final String[] PROTOCOL_LISTS = new String[]{"http://", "https://"};
private static final List PROTOCOLS = Arrays.asList(PROTOCOL_LISTS);
- // Used when the actual version can not be parsed.
- private static final double DEFAULT_JAVA_VERSION = 0.0;
+ private static final ComparableVersion JAVA_VERSION = getJavaVersion();
// Indicates whether we've overridden HttpURLConnection's behaviour to
// allow PATCH requests yet. Also see docs on allowPatchRequestsIfNeeded().
@@ -61,7 +72,7 @@ public class MessageBirdServiceImpl implements MessageBirdService {
private final String accessKey;
private final String serviceUrl;
- private final String clientVersion = "3.0.6";
+ private final String clientVersion = "6.3.1";
private final String userAgentString;
private Proxy proxy = null;
@@ -75,17 +86,20 @@ public MessageBirdServiceImpl(final String accessKey, final String serviceUrl) {
this.accessKey = accessKey;
this.serviceUrl = serviceUrl;
this.userAgentString = determineUserAgentString();
+
}
- private String determineUserAgentString() {
- double javaVersion = DEFAULT_JAVA_VERSION;
+ private static ComparableVersion getJavaVersion() {
try {
- javaVersion = getVersion();
- } catch (GeneralException e) {
- // Do nothing: leave the version at its default.
+ String version = System.getProperty("java.version");
+ return new ComparableVersion(version);
+ } catch (IllegalArgumentException e) {
+ return new ComparableVersion("0.0");
}
+ }
- return String.format("MessageBird Java/%s ApiClient/%s", javaVersion, clientVersion);
+ private String determineUserAgentString() {
+ return String.format("MessageBird Java/%s ApiClient/%s", JAVA_VERSION, clientVersion);
}
/**
@@ -97,6 +111,12 @@ public MessageBirdServiceImpl(final String accessKey) {
this(accessKey, "https://rest.messagebird.com");
}
+ @Override
+ public R request(String request, Class clazz)
+ throws UnauthorizedException, GeneralException, NotFoundException {
+ return getJsonData(request, null, "GET", clazz);
+ }
+
@Override
public R requestByID(String request, String id, Class clazz) throws UnauthorizedException, GeneralException, NotFoundException {
String path = "";
@@ -120,11 +140,27 @@ public R requestByID(String request, String id, Map params,
return getJsonData(request + path + queryParams, null, "GET", clazz);
}
+ @Override
+ public List requestByIdAsList(String request, String id, Class elementClass)
+ throws UnauthorizedException, GeneralException, NotFoundException {
+ String path = "";
+ if (id != null) {
+ path = "/" + id;
+ }
+
+ return getJsonDataAsList(request + path, null, "GET", elementClass);
+ }
+
@Override
public void deleteByID(String request, String id) throws UnauthorizedException, GeneralException, NotFoundException {
getJsonData(request + "/" + id, null, "DELETE", null);
}
+ @Override
+ public R delete(String request, Class clazz) throws UnauthorizedException, GeneralException, NotFoundException {
+ return getJsonData(request, null, "DELETE", clazz);
+ }
+
@Override
public R requestList(String request, Integer offset, Integer limit, Class clazz) throws UnauthorizedException, GeneralException {
Map map = new LinkedHashMap<>();
@@ -137,6 +173,17 @@ public R requestList(String request, Integer offset, Integer limit, Class
}
}
+ @Override
+ public R requestList(String request, Map params, Integer offset, Integer limit, Class clazz) throws UnauthorizedException, GeneralException {
+ if (offset != null) params.put("offset", String.valueOf(offset));
+ if (limit != null) params.put("limit", String.valueOf(limit));
+ try {
+ return getJsonData(request + "?" + getPathVariables(params), null, "GET", clazz);
+ } catch (NotFoundException e) {
+ throw new GeneralException(e);
+ }
+ }
+
@Override
public R requestList(String request, PagedPaging pagedPaging, Class clazz) throws UnauthorizedException, GeneralException {
Map map = new LinkedHashMap<>();
@@ -157,12 +204,17 @@ public R sendPayLoad(String request, P payload, Class clazz) throws Un
@Override
public R sendPayLoad(String method, String request, P payload, Class clazz) throws UnauthorizedException, GeneralException {
+ return sendPayLoad(method, request, new HashMap<>(), payload, clazz);
+ }
+
+ @Override
+ public R sendPayLoad(String method, String request, Map headers, P payload, Class clazz) throws UnauthorizedException, GeneralException {
if (!REQUEST_METHODS_WITH_PAYLOAD.contains(method)) {
throw new IllegalArgumentException(String.format(REQUEST_METHOD_NOT_ALLOWED, method));
}
try {
- return getJsonData(request, payload, method, clazz);
+ return getJsonData(request, payload, method, headers, clazz);
} catch (NotFoundException e) {
throw new GeneralException(e);
}
@@ -192,6 +244,14 @@ public String getBinaryData(String request, String basePath, String fileName) th
public T getJsonData(final String request, final P payload, final String requestType, final Class clazz) throws UnauthorizedException, GeneralException, NotFoundException {
+ return getJsonData(request, payload, requestType, new HashMap<>(), clazz);
+ }
+
+ public
List getJsonDataAsList(final String request, final P payload, final String requestType, final Class elementClass) throws UnauthorizedException, GeneralException, NotFoundException {
+ return getJsonDataAsList(request, payload, requestType, new HashMap<>(), elementClass);
+ }
+
+ public T getJsonData(final String request, final P payload, final String requestType, final Map headers, final Class clazz) throws UnauthorizedException, GeneralException, NotFoundException {
if (request == null) {
throw new IllegalArgumentException(REQUEST_VALUE_MUST_BE_SPECIFIED);
}
@@ -200,23 +260,24 @@ public T getJsonData(final String request, final P payload, final String
if (!isURLAbsolute(url)) {
url = serviceUrl + url;
}
-
- final APIResponse apiResponse = doRequest(requestType, url, payload);
+ final APIResponse apiResponse = doRequest(requestType, url, headers, payload);
final String body = apiResponse.getBody();
final int status = apiResponse.getStatus();
- if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_CREATED) {
- final ObjectMapper mapper = new ObjectMapper();
-
- // If we as new properties, we don't want the system to fail, we rather want to ignore them
- mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
- // Enable case insensitivity to avoid parsing errors if parameters' case in api response doesn't match sdk's
- mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
- mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
-
+ if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_CREATED || status == HttpURLConnection.HTTP_ACCEPTED) {
try {
- return mapper.readValue(body, clazz);
+ final ObjectMapper mapper = new ObjectMapper();
+ // If we as new properties, we don't want the system to fail, we rather want to ignore them
+ mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ // Enable case insensitivity to avoid parsing errors if parameters' case in api response doesn't match sdk's
+ mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
+ mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
+
+ // Prevents mismatched exception when clazz is null
+ return clazz == null
+ ? null
+ : this.readValue(mapper, body, clazz);
} catch (IOException ioe) {
throw new GeneralException(ioe);
}
@@ -227,6 +288,54 @@ public T getJsonData(final String request, final P payload, final String
return null;
}
+ // todo: need to refactor for duplicated code.
+ public
List getJsonDataAsList(final String request,
+ final P payload, final String requestType, final Map headers, final Class elementClass)
+ throws UnauthorizedException, GeneralException, NotFoundException {
+ if (request == null) {
+ throw new IllegalArgumentException(REQUEST_VALUE_MUST_BE_SPECIFIED);
+ }
+
+ String url = request;
+ if (!isURLAbsolute(url)) {
+ url = serviceUrl + url;
+ }
+ final APIResponse apiResponse = doRequest(requestType, url, headers, payload);
+
+ final String body = apiResponse.getBody();
+ final int status = apiResponse.getStatus();
+
+ if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_CREATED || status == HttpURLConnection.HTTP_ACCEPTED) {
+ try {
+ final ObjectMapper mapper = new ObjectMapper();
+ // If we as new properties, we don't want the system to fail, we rather want to ignore them
+ mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ // Enable case insensitivity to avoid parsing errors if parameters' case in api response doesn't match sdk's
+ mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
+ mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
+
+ // Prevents mismatched exception when clazz is null
+ return this.readValueAsList(mapper, body, elementClass);
+ } catch (IOException ioe) {
+ throw new GeneralException(ioe);
+ }
+ } else if (status == HttpURLConnection.HTTP_NO_CONTENT) {
+ return Collections.emptyList(); // no content doesn't mean an error
+ }
+ handleHttpFailStatuses(status, body);
+ return Collections.emptyList();
+ }
+
+ private T readValue(ObjectMapper mapper, String content, Class clazz)
+ throws JsonProcessingException {
+ return mapper.readValue(content, clazz);
+ }
+
+ private List readValueAsList(ObjectMapper mapper, String content, final Class elementClass)
+ throws JsonProcessingException {
+ return mapper.readValue(content, mapper.getTypeFactory().constructCollectionType(List.class, elementClass));
+ }
+
private void handleHttpFailStatuses(final int status, String body) throws UnauthorizedException, NotFoundException, GeneralException {
if (status == HttpURLConnection.HTTP_UNAUTHORIZED) {
final List errorReport = getErrorReportOrNull(body);
@@ -246,11 +355,12 @@ private void handleHttpFailStatuses(final int status, String body) throws Unauth
*
* @param method HTTP method.
* @param url Absolute URL.
+ * @param headers additional headers to set on the request.
* @param payload Payload to JSON encode for the request body. May be null.
* @param
Type of the payload.
* @return APIResponse containing the response's body and status.
*/
-
APIResponse doRequest(final String method, final String url, final P payload) throws GeneralException {
+
APIResponse doRequest(final String method, final String url, final Map headers, final P payload) throws GeneralException {
HttpURLConnection connection = null;
InputStream inputStream = null;
@@ -265,13 +375,16 @@
APIResponse doRequest(final String method, final String url, final P payload
}
try {
- connection = getConnection(url, payload, method);
+ connection = getConnection(url, payload, method, headers);
int status = connection.getResponseCode();
if (APIResponse.isSuccessStatus(status)) {
inputStream = connection.getInputStream();
} else {
inputStream = connection.getErrorStream();
+ if (inputStream == null) {
+ throw new IOException("Server returned HTTP error code " + status + " with no body.");
+ }
}
return new APIResponse(readToEnd(inputStream), status);
@@ -440,6 +553,20 @@ private boolean isURLAbsolute(String url) {
* @throws IOException io exception
*/
public
HttpURLConnection getConnection(final String serviceUrl, final P body, final String requestType) throws IOException {
+ return getConnection(serviceUrl, body, requestType, new HashMap<>());
+ }
+
+ /**
+ * Create a HttpURLConnection connection object
+ *
+ * @param serviceUrl URL that needs to be requested
+ * @param body body could not be empty for POST or PUT requests
+ * @param requestType Request type POST requests without a payload will generate a exception
+ * @param headers additional headers to set on the request
+ * @return base class
+ * @throws IOException io exception
+ */
+ public
HttpURLConnection getConnection(final String serviceUrl, final P body, final String requestType, final Map headers) throws IOException {
if (requestType == null || !REQUEST_METHODS.contains(requestType)) {
throw new IllegalArgumentException(String.format(REQUEST_METHOD_NOT_ALLOWED, requestType));
}
@@ -469,58 +596,54 @@ public
HttpURLConnection getConnection(final String serviceUrl, final P body
connection.setRequestProperty("Content-Type", "application/json");
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
-
// Specifically set the date format for POST requests so scheduled
// messages and other things relying on specific date formats don't
// fail when sending.
DateFormat df = getDateFormat();
mapper.setDateFormat(df);
- final String json = mapper.writeValueAsString(body);
- connection.getOutputStream().write(json.getBytes(String.valueOf(StandardCharsets.UTF_8)));
+ setAdditionalHeaders(connection, headers);
+
+ byte[] bodyBytes;
+ if (body instanceof byte[]) {
+ bodyBytes = (byte[]) body;
+ } else {
+ final String json = mapper.writeValueAsString(body);
+ bodyBytes = json.getBytes(StandardCharsets.UTF_8);
+ }
+ connection.getOutputStream().write(bodyBytes);
} else if ("DELETE".equals(requestType)) {
// could have just used rquestType as it is
connection.setDoOutput(false);
connection.setRequestMethod("DELETE");
connection.setRequestProperty("Content-Type", "text/plain");
+
+ setAdditionalHeaders(connection, headers);
} else {
connection.setDoOutput(false);
connection.setRequestMethod("GET");
connection.setRequestProperty("Content-Type", "text/plain");
+
+ setAdditionalHeaders(connection, headers);
}
return connection;
}
- private DateFormat getDateFormat() {
- double javaVersion = DEFAULT_JAVA_VERSION;
- try {
- javaVersion = getVersion();
- } catch (GeneralException e) {
- // Do nothing: leave the version at its default.
+ private void setAdditionalHeaders(HttpURLConnection connection, Map headers) {
+ for (Map.Entry header : headers.entrySet()) {
+ connection.setRequestProperty(header.getKey(), header.getValue());
}
+ }
- if (javaVersion > 1.6) {
+ private DateFormat getDateFormat() {
+ ComparableVersion java6 = new ComparableVersion("1.6");
+ if (JAVA_VERSION.compareTo(java6) > 0) {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
}
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ");
}
- private double getVersion() throws GeneralException {
- String version = System.getProperty("java.version");
-
- try {
- int pos = version.indexOf('.');
- pos = version.indexOf('.', pos + 1);
-
- return Double.parseDouble(version.substring(0, pos));
- } catch (RuntimeException e) {
- // Thrown if the index is out of bounds, or when we can't parse a
- // double for some reason.
- throw new GeneralException(e);
- }
- }
-
/**
* Get the MessageBird error report data.
*
@@ -529,9 +652,12 @@ private double getVersion() throws GeneralException {
*/
private List getErrorReportOrNull(final String body) {
ObjectMapper objectMapper = new ObjectMapper();
-
try {
JsonNode jsonNode = objectMapper.readValue(body, JsonNode.class);
+ if(!jsonNode.has("errors")) {
+ return null;
+ }
+
ErrorReport[] errors = objectMapper.readValue(jsonNode.get("errors").toString(), ErrorReport[].class);
List result = Arrays.asList(errors);
@@ -618,6 +744,17 @@ private void saveClose(final InputStream is) {
}
}
+ /**
+ * Encodes a key/value pair with percent encoding.
+ *
+ * @param key the key name to be used
+ * @param value the value to be assigned to that key
+ * @return String
+ */
+ private String encodeKeyValuePair(String key, Object value) throws UnsupportedEncodingException {
+ return URLEncoder.encode(key, String.valueOf(StandardCharsets.UTF_8)) + "=" + URLEncoder.encode(String.valueOf(value), String.valueOf(StandardCharsets.UTF_8));
+ }
+
/**
* Build a path variable for GET requests
*
@@ -631,7 +768,30 @@ private String getPathVariables(final Map map) {
bpath.append("&");
}
try {
- bpath.append(URLEncoder.encode(param.getKey(), String.valueOf(StandardCharsets.UTF_8))).append("=").append(URLEncoder.encode(String.valueOf(param.getValue()), String.valueOf(StandardCharsets.UTF_8)));
+ // Check to see if the value is a Collection
+ if (param.getValue() instanceof Collection) {
+ // If it is, cast the value as a Collection explicitly
+ // so it can be iterated over. Its values should be
+ // appended to the querystring parameters using the
+ // original key provided (e.g., ?features=sms&features=mms)
+ Collection> col = (Collection>) param.getValue();
+ Iterator> iterator = col.iterator();
+ int count = 0;
+ // While there are still remaining iterables
+ while (iterator.hasNext()) {
+ // Append & if not the first iterable
+ if (count > 0) {
+ bpath.append("&");
+ }
+ // Append the encoded querystring key/value pair.
+ // the value is returned from the next() call
+ bpath.append(encodeKeyValuePair(param.getKey(), iterator.next()));
+ count++;
+ }
+ } else {
+ // If the value is not a collection, create the querystring value directly.
+ bpath.append(encodeKeyValuePair(param.getKey(), param.getValue()));
+ }
} catch (UnsupportedEncodingException exception) {
// Do nothing
}
diff --git a/api/src/main/java/com/messagebird/Request.java b/api/src/main/java/com/messagebird/Request.java
index 41d9283b..46048b0e 100644
--- a/api/src/main/java/com/messagebird/Request.java
+++ b/api/src/main/java/com/messagebird/Request.java
@@ -5,7 +5,10 @@
/**
* Holds request data needed to calculate a signature hash for incoming
* webhooks.
+ *
+ * @deprecated This class is being deprecated together with {@link RequestSigner}
*/
+@Deprecated
public class Request {
private final String timestamp;
@@ -17,11 +20,13 @@ public class Request {
/**
* Constructs a new request instance.
*
- * @param timestamp Timestamp provided in the MessageBird-Request-Timestamp
- * header.
+ * @param timestamp Timestamp provided in the MessageBird-Request-Timestamp
+ * header.
* @param queryParameters Query parameters in abc=foo&def=ghi format.
- * @param data Raw body of this request.
+ * @param data Raw body of this request.
+ * @deprecated
*/
+ @Deprecated
public Request(String timestamp, String queryParameters, byte[] data) {
if (timestamp == null || timestamp.isEmpty()) {
throw new IllegalArgumentException("Timestamp can not be null or empty");
diff --git a/api/src/main/java/com/messagebird/RequestSigner.java b/api/src/main/java/com/messagebird/RequestSigner.java
index e2f91d93..d79e464c 100644
--- a/api/src/main/java/com/messagebird/RequestSigner.java
+++ b/api/src/main/java/com/messagebird/RequestSigner.java
@@ -4,26 +4,30 @@
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
-import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
+import java.util.Base64;
/**
* RequestSigner is used to verify HTTP requests and is an implementation of:
* https://developers.messagebird.com/docs/verify-http-requests. Retrieve your
* signing key at https://dashboard.messagebird.com/developers/settings.
+ *
+ * @deprecated This class is being deprecated.
+ *
Use {@link RequestValidator} instead.
*/
+@Deprecated
public class RequestSigner {
private static final String ALGORITHM_SHA256 = "SHA-256";
private static final String ALGORITHM_HMAC_SHA256 = "HmacSHA256";
private static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
- private SecretKeySpec secret;
+ private final SecretKeySpec secret;
/**
* Constructs a new RequestSigner instance.
@@ -31,7 +35,9 @@ public class RequestSigner {
* @param key Signing key. Can be retrieved through
* https://dashboard.messagebird.com/developers/settings. This
* is NOT your API key.
+ * @deprecated Use {@link RequestValidator#RequestValidator(String)} )} instead.
*/
+ @Deprecated
public RequestSigner(byte[] key) {
this.secret = new SecretKeySpec(key, ALGORITHM_HMAC_SHA256);
}
@@ -42,13 +48,15 @@ public RequestSigner(byte[] key) {
*
* @param expectedSignature Signature from the MessageBird-Signature
* header in its original base64 encoded state.
- * @param request Request containing the values from the incoming webhook.
+ * @param request Request containing the values from the incoming webhook.
* @return True if the computed signature matches the expected signature.
+ * @deprecated Use {@link RequestValidator#validateSignature(String, String, byte[])} instead.
*/
+ @Deprecated
public boolean isMatch(String expectedSignature, Request request) {
try {
- return isMatch(Base64.decode(expectedSignature), request);
- } catch (IOException e) {
+ return isMatch(Base64.getDecoder().decode(expectedSignature), request);
+ } catch (IllegalArgumentException e) {
throw new RequestSigningException(e);
}
}
@@ -59,9 +67,11 @@ public boolean isMatch(String expectedSignature, Request request) {
*
* @param expectedSignature Decoded (with base64) signature
* from the MessageBird-Signature header
- * @param request Request containing the values from the incoming webhook.
+ * @param request Request containing the values from the incoming webhook.
* @return True if the computed signature matches the expected signature.
+ * @deprecated Use {@link RequestValidator#validateSignature(String, String, byte[])} instead.
*/
+ @Deprecated
public boolean isMatch(byte[] expectedSignature, Request request) {
return Arrays.equals(computeSignature(request), expectedSignature);
}
@@ -93,7 +103,7 @@ private byte[] getSha256Hash(byte[] bytes) {
/**
* Stitches the two arrays together and returns a new one.
*
- * @param first Start of the new array.
+ * @param first Start of the new array.
* @param second End of the new array.
* @return New array based on first and second.
*/
diff --git a/api/src/main/java/com/messagebird/RequestValidator.java b/api/src/main/java/com/messagebird/RequestValidator.java
new file mode 100644
index 00000000..24ae6343
--- /dev/null
+++ b/api/src/main/java/com/messagebird/RequestValidator.java
@@ -0,0 +1,204 @@
+package com.messagebird;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier.BaseVerification;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.exceptions.SignatureVerificationException;
+import com.auth0.jwt.interfaces.Claim;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.auth0.jwt.interfaces.JWTVerifier;
+import com.messagebird.exceptions.RequestValidationException;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Clock;
+
+/**
+ * RequestValidator validates request signature signed by MessageBird services.
+ *
+ * @see Verify HTTP Requests
+ */
+public class RequestValidator {
+
+ /**
+ * Signature of signed request is set with header name 'MessageBird-Signature-JWT'
+ */
+ public static final String SIGNATURE_HEADER = "MessageBird-Signature-JWT";
+ private static final String ALGORITHM_SHA256 = "SHA-256";
+ private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
+ 'e', 'f'};
+
+ private final Algorithm HMAC256, HMAC384, HMAC512;
+
+ /**
+ * This field instructs Validator to not validate url_hash claim.
+ * It is recommended to not skip URL validation to ensure high security.
+ * but the ability to skip URL validation is necessary in some cases, e.g.
+ * your service is behind proxy or when you want to validate it yourself.
+ * Note that when true, no query parameters should be trusted.
+ * Defaults to false.
+ */
+ private final boolean skipURLValidation;
+
+ /**
+ * RequestValidator validates request signature with a customer signature key.
+ *
+ * @param signatureKey customer signature key. Can be retrieved through
+ * Developer Settings.
+ * This is NOT your API key.
+ * @see Verify HTTP Requests
+ */
+ public RequestValidator(String signatureKey) {
+ this(signatureKey, false);
+ }
+
+ /**
+ * RequestValidator validates webhook signature with a customer signature key.
+ *
+ * @param signatureKey customer signature key. Can be retrieved through
+ * Developer Settings.
+ * This is NOT your API key.
+ * @param skipURLValidation whether url_hash claim validation should be skipped.
+ * Note that when true, no query parameters should be trusted.
+ * @see Verify HTTP Requests
+ */
+ public RequestValidator(String signatureKey, boolean skipURLValidation) {
+ this.HMAC256 = Algorithm.HMAC256(signatureKey);
+ this.HMAC384 = Algorithm.HMAC384(signatureKey);
+ this.HMAC512 = Algorithm.HMAC512(signatureKey);
+ this.skipURLValidation = skipURLValidation;
+ }
+
+ /**
+ * Returns raw signature payload after validating a signature successfully,
+ * otherwise throws {@code RequestValidationException}.
+ *
+ * This JWT is signed with a MessageBird account unique secret key, ensuring the request is from MessageBird and
+ * a specific account.
+ * The JWT contains the following claims:
+ *
+ *
+ *
"url_hash" - the raw URL hashed with SHA256 ensuring the URL wasn't altered.
+ *
"payload_hash" - the raw payload hashed with SHA256 ensuring the payload wasn't altered.
+ *
"jti" - a unique token ID to implement an optional non-replay check (NOT validated by default).
+ *
"nbf" - the not before timestamp.
+ *
"exp" - the expiration timestamp is ensuring that a request isn't captured and used at a later time.
+ *
"iss" - the issuer name, always MessageBird.
+ *
+ *
+ * @param clock custom {@link Clock} instance to validate timestamp claims.
+ * @param signature the actual signature.
+ * @param url the raw url including the protocol, hostname and query string,
+ * {@code https://example.com/?example=42}.
+ * @param requestBody the raw request body.
+ * @return raw signature payload as {@link DecodedJWT} object.
+ * @throws RequestValidationException when the signature is invalid.
+ * @see Verify HTTP Requests
+ */
+ public DecodedJWT validateSignature(Clock clock, String signature, String url, byte[] requestBody)
+ throws RequestValidationException {
+ if (signature == null || signature.length() == 0)
+ throw new RequestValidationException("The signature can not be empty.");
+
+ if (!skipURLValidation && (url == null || url.length() == 0))
+ throw new RequestValidationException("The url can not be empty.");
+
+ DecodedJWT jwt = JWT.decode(signature);
+
+ Algorithm algorithm;
+ switch (jwt.getAlgorithm()) {
+ case "HS256":
+ algorithm = HMAC256;
+ break;
+ case "HS384":
+ algorithm = HMAC384;
+ break;
+ case "HS512":
+ algorithm = HMAC512;
+ break;
+ default:
+ throw new RequestValidationException(String.format("The signing method '%s' is invalid.", jwt.getAlgorithm()));
+ }
+
+ BaseVerification builder = (BaseVerification) JWT.require(algorithm)
+ .withIssuer("MessageBird")
+ .ignoreIssuedAt()
+ .acceptLeeway(1);
+
+ if (!skipURLValidation)
+ builder.withClaim("url_hash", calculateSha256(url.getBytes()));
+
+ Claim payloadHashClaim = jwt.getClaim("payload_hash");
+ boolean payloadHashClaimExist = !(payloadHashClaim.isNull() || payloadHashClaim.isMissing());
+ if (requestBody != null && requestBody.length > 0) {
+ if (!payloadHashClaimExist) {
+ throw new RequestValidationException("The Claim 'payload_hash' is not set but payload is present.");
+ }
+ builder.withClaim("payload_hash", calculateSha256(requestBody));
+ } else if (payloadHashClaimExist) {
+ throw new RequestValidationException("The Claim 'payload_hash' is set but actual payload is missing.");
+ }
+
+ JWTVerifier verifier = clock == null ? builder.build() : builder.build(clock);
+
+ try {
+ return verifier.verify(jwt);
+ } catch (SignatureVerificationException e) {
+ throw new RequestValidationException("Signature is invalid.", e);
+ } catch (JWTVerificationException e) {
+ throw new RequestValidationException(e.getMessage(), e.getCause());
+ }
+ }
+
+ /**
+ * Returns raw signature payload after validating a signature successfully,
+ * otherwise throws {@code RequestValidationException}.
+ *
+ * @param signature the actual signature.
+ * @param url the raw url including the protocol, hostname and query string,
+ * {@code https://example.com/?example=42}.
+ * @param requestBody the raw request body.
+ * @return raw signature payload as {@link DecodedJWT} object.
+ * @throws RequestValidationException when the signature is invalid.
+ * @see RequestValidator#validateSignature(Clock, String, String, byte[])
+ */
+ public DecodedJWT validateSignature(String signature, String url, byte[] requestBody)
+ throws RequestValidationException {
+ return validateSignature(null, signature, url, requestBody);
+ }
+
+ /**
+ * Validates request signature with URL validation disabled.
+ * Note that no query parameters should be trusted and this only works if {@code RequestValidator} is constructed
+ * with {@code skipURLValidation} set to true.
+ *
+ * @param signature the actual signature.
+ * @param requestBody the raw request body.
+ * @return raw signature payload as {@link DecodedJWT} object.
+ * @throws RequestValidationException when the signature is invalid.
+ * @see RequestValidator#validateSignature(String, String, byte[])
+ */
+ public DecodedJWT validateSignature(String signature, byte[] requestBody)
+ throws RequestValidationException {
+ return validateSignature(null, signature, null, requestBody);
+ }
+
+ private static String calculateSha256(byte[] bytes) {
+ try {
+ return encodeHex(MessageDigest.getInstance(ALGORITHM_SHA256).digest(bytes));
+ } catch (NoSuchAlgorithmException e) {
+ throw new RequestValidationException(e);
+ }
+ }
+
+ private static String encodeHex(final byte[] data) {
+ final int l = data.length;
+ final char[] out = new char[l << 1];
+ for (int i = 0, j = 0; i < l; i++) {
+ out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4];
+ out[j++] = HEX_DIGITS[0x0F & data[i]];
+ }
+ return new String(out);
+ }
+}
diff --git a/api/src/main/java/com/messagebird/exceptions/RequestSigningException.java b/api/src/main/java/com/messagebird/exceptions/RequestSigningException.java
index 9f993375..d69a1922 100644
--- a/api/src/main/java/com/messagebird/exceptions/RequestSigningException.java
+++ b/api/src/main/java/com/messagebird/exceptions/RequestSigningException.java
@@ -2,7 +2,10 @@
/**
* Thrown if an error occurs during request signing.
+ *
+ * @deprecated This class is being deprecated together with {@link com.messagebird.RequestSigner}
*/
+@Deprecated
public class RequestSigningException extends RuntimeException {
public RequestSigningException() {
diff --git a/api/src/main/java/com/messagebird/exceptions/RequestValidationException.java b/api/src/main/java/com/messagebird/exceptions/RequestValidationException.java
new file mode 100644
index 00000000..f759b211
--- /dev/null
+++ b/api/src/main/java/com/messagebird/exceptions/RequestValidationException.java
@@ -0,0 +1,22 @@
+package com.messagebird.exceptions;
+
+/**
+ * Thrown if an error occurs during request signing.
+ */
+public class RequestValidationException extends RuntimeException {
+
+ public RequestValidationException() {
+ }
+
+ public RequestValidationException(String message) {
+ super(message);
+ }
+
+ public RequestValidationException(Throwable cause) {
+ super(cause);
+ }
+
+ public RequestValidationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/AccessKey.java b/api/src/main/java/com/messagebird/objects/AccessKey.java
new file mode 100644
index 00000000..4d529711
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/AccessKey.java
@@ -0,0 +1,98 @@
+package com.messagebird.objects;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+public class AccessKey {
+ private String id;
+ @JsonProperty("access_key")
+ private String accessKey;
+ private String mode;
+ private String description;
+ @JsonProperty("core_user_id")
+ private int coreUserId;
+ @JsonProperty("user_id")
+ private int userId;
+ @JsonProperty("external_id")
+ private int externalId;
+ private List roles;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getAccessKey() {
+ return accessKey;
+ }
+
+ public void setAccessKey(String accessKey) {
+ this.accessKey = accessKey;
+ }
+
+ public String getMode() {
+ return mode;
+ }
+
+ public void setMode(String mode) {
+ this.mode = mode;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public int getCoreUserId() {
+ return coreUserId;
+ }
+
+ public void setCoreUserId(int coreUserId) {
+ this.coreUserId = coreUserId;
+ }
+
+ public int getUserId() {
+ return userId;
+ }
+
+ public void setUserId(int userId) {
+ this.userId = userId;
+ }
+
+ public int getExternalId() {
+ return externalId;
+ }
+
+ public void setExternalId(int externalId) {
+ this.externalId = externalId;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+
+ @Override
+ public String toString() {
+ return "AccessKey{" +
+ "id='" + id + '\'' +
+ ", access_key='" + accessKey + '\'' +
+ ", mod='" + mode + '\'' +
+ ", description='" + description + '\'' +
+ ", core_user_id=" + coreUserId +
+ ", user_id=" + userId +
+ ", external_id=" + externalId +
+ ", roles=" + roles +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/ChildAccountCreateResponse.java b/api/src/main/java/com/messagebird/objects/ChildAccountCreateResponse.java
new file mode 100644
index 00000000..40ec38a4
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/ChildAccountCreateResponse.java
@@ -0,0 +1,53 @@
+package com.messagebird.objects;
+
+import java.util.List;
+
+public class ChildAccountCreateResponse extends ChildAccountResponse{
+ private List accessKeys;
+ private String signingKey;
+ private String invoiceAggregation;
+ private String paymentMoment;
+
+ public List getAccessKeys() {
+ return accessKeys;
+ }
+
+ public void setAccessKeys(List accessKeys) {
+ this.accessKeys = accessKeys;
+ }
+
+ public String getSigningKey() {
+ return signingKey;
+ }
+
+ public void setSigningKey(String signingKey) {
+ this.signingKey = signingKey;
+ }
+
+ public String getInvoiceAggregation() {
+ return invoiceAggregation;
+ }
+
+ public void setInvoiceAggregation(String invoiceAggregation) {
+ this.invoiceAggregation = invoiceAggregation;
+ }
+
+ public String getPaymentMoment() {
+ return paymentMoment;
+ }
+
+ public void setPaymentMoment(String paymentMoment) {
+ this.paymentMoment = paymentMoment;
+ }
+
+ @Override
+ public String toString() {
+ return "ChildAccountCreateResponse{" +
+ "id='" + getId() + '\'' +
+ ", name='" + getName() + '\'' +
+ ", accessKeys=" + accessKeys + '\'' +
+ ", invoiceAggregation='" + invoiceAggregation + '\'' +
+ ", paymentMoment='" + paymentMoment + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/ChildAccountDetailedResponse.java b/api/src/main/java/com/messagebird/objects/ChildAccountDetailedResponse.java
new file mode 100644
index 00000000..e39d5969
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/ChildAccountDetailedResponse.java
@@ -0,0 +1,22 @@
+package com.messagebird.objects;
+
+public class ChildAccountDetailedResponse extends ChildAccountResponse{
+ private String invoiceAggregation;
+
+ public String getInvoiceAggregation() {
+ return invoiceAggregation;
+ }
+
+ public void setInvoiceAggregation(String invoiceAggregation) {
+ this.invoiceAggregation = invoiceAggregation;
+ }
+
+ @Override
+ public String toString() {
+ return "ChildAccountDetailedResponse{" +
+ "id='" + getId() + '\'' +
+ ", name='" + getName() + '\'' +
+ ", invoiceAggregation='" + invoiceAggregation + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/ChildAccountRequest.java b/api/src/main/java/com/messagebird/objects/ChildAccountRequest.java
new file mode 100644
index 00000000..aa0e9e5f
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/ChildAccountRequest.java
@@ -0,0 +1,13 @@
+package com.messagebird.objects;
+
+public class ChildAccountRequest {
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/ChildAccountResponse.java b/api/src/main/java/com/messagebird/objects/ChildAccountResponse.java
new file mode 100644
index 00000000..de232f05
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/ChildAccountResponse.java
@@ -0,0 +1,34 @@
+package com.messagebird.objects;
+
+import java.io.Serializable;
+
+public class ChildAccountResponse implements Serializable {
+ private static final long serialVersionUID = -8605510461438669942L;
+
+ private String id;
+ private String name;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return "ChildAccountResponse{" +
+ "id='" + id + '\'' +
+ ", name='" + name + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/DataCodingType.java b/api/src/main/java/com/messagebird/objects/DataCodingType.java
index 986cea4a..3f78d21d 100644
--- a/api/src/main/java/com/messagebird/objects/DataCodingType.java
+++ b/api/src/main/java/com/messagebird/objects/DataCodingType.java
@@ -7,7 +7,8 @@
*/
public enum DataCodingType {
plain("plain"),
- unicode("unicode");
+ unicode("unicode"),
+ auto("auto");
final String value;
diff --git a/api/src/main/java/com/messagebird/objects/ErrorReport.java b/api/src/main/java/com/messagebird/objects/ErrorReport.java
index 00c74aa9..afd7dcf9 100644
--- a/api/src/main/java/com/messagebird/objects/ErrorReport.java
+++ b/api/src/main/java/com/messagebird/objects/ErrorReport.java
@@ -1,32 +1,46 @@
package com.messagebird.objects;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import java.io.Serializable;
+
/**
* When MessageBird returns a 4xx, you will find a list of any error codes in your return dataset.
* you will receive a list of errors from the API in such case.
*
* Created by rvt on 1/5/15.
*/
-public class ErrorReport {
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class ErrorReport implements Serializable {
+
+ private static final long serialVersionUID = -8611665867089703268L;
+
private Integer code;
private String description;
private String parameter;
+ private String message;
public ErrorReport() {
}
- public ErrorReport(Integer code, String description, String parameter) {
+ public ErrorReport(Integer code, String description, String parameter, String message) {
this.code = code;
this.description = description;
this.parameter = parameter;
+ this.message = message;
}
@Override
public String toString() {
- return "ErrorReport{" +
- "code=" + code +
- ", description='" + description + '\'' +
- ", parameter='" + parameter + '\'' +
- '}';
+ String str = "ErrorReport{code=" + code;
+ if (message != null && !message.isEmpty()) {
+ str = str.concat(", message='" + message + "'");
+ } else {
+ str = str.concat(", description='" + description + "'");
+ str = str.concat(", parameter='" + parameter + "'");
+ }
+ str = str.concat("}");
+ return str;
}
/**
@@ -53,4 +67,11 @@ public String getParameter() {
return parameter;
}
+ /**
+ * message not null for only voice API response
+ * @return
+ */
+ public String getMessage() {
+ return message;
+ }
}
diff --git a/api/src/main/java/com/messagebird/objects/FileUploadResponse.java b/api/src/main/java/com/messagebird/objects/FileUploadResponse.java
new file mode 100644
index 00000000..29155a63
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/FileUploadResponse.java
@@ -0,0 +1,21 @@
+package com.messagebird.objects;
+
+public class FileUploadResponse {
+
+ private String id;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "FileUploadResponse{" +
+ "id='" + id + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/Language.java b/api/src/main/java/com/messagebird/objects/Language.java
index 6ce76027..ccb63351 100644
--- a/api/src/main/java/com/messagebird/objects/Language.java
+++ b/api/src/main/java/com/messagebird/objects/Language.java
@@ -1,5 +1,7 @@
package com.messagebird.objects;
+import com.fasterxml.jackson.annotation.JsonValue;
+
/**
* Created by faizan on 09/12/15.
*/
@@ -11,7 +13,7 @@ public enum Language {
EN_US("en-us"),
ES_ES("es-es"),
FR_FR("fr-fr"),
- RU_RU("ru_ru"),
+ RU_RU("ru-ru"),
ZH_CN("zh-cn"),
EN_AU("en-au"),
ES_MX("es-mx"),
@@ -25,13 +27,21 @@ public enum Language {
PT_BR("pt-br"),
RO_RO("ro-ro");
- private String code;
+ final String code;
Language(String code) {
this.code = code;
}
+ @JsonValue
+ public String getCode() {
+ return code;
+ }
+
+ @Override
public String toString() {
- return this.code;
+ return "Language{" +
+ "code='" + code + '\'' +
+ '}';
}
}
diff --git a/api/src/main/java/com/messagebird/objects/ListBase.java b/api/src/main/java/com/messagebird/objects/ListBase.java
index 354eafa1..da54e709 100644
--- a/api/src/main/java/com/messagebird/objects/ListBase.java
+++ b/api/src/main/java/com/messagebird/objects/ListBase.java
@@ -13,6 +13,7 @@ public class ListBase {
private Integer limit;
private Integer totalCount;
private Links links;
+ private List items;
public ListBase() {
}
@@ -28,7 +29,6 @@ public String toString() {
'}';
}
- private List items;
public Integer getOffset() {
return offset;
diff --git a/api/src/main/java/com/messagebird/objects/MClassType.java b/api/src/main/java/com/messagebird/objects/MClassType.java
index 1f566703..4474b15e 100644
--- a/api/src/main/java/com/messagebird/objects/MClassType.java
+++ b/api/src/main/java/com/messagebird/objects/MClassType.java
@@ -30,7 +30,6 @@ public Integer toJson() {
return getValue();
}
- @JsonCreator
public static MClassType forValue(String value) {
if ("0".equals(value)) {
return flash;
diff --git a/api/src/main/java/com/messagebird/objects/MessageReference.java b/api/src/main/java/com/messagebird/objects/MessageReference.java
index da5fb596..26778292 100644
--- a/api/src/main/java/com/messagebird/objects/MessageReference.java
+++ b/api/src/main/java/com/messagebird/objects/MessageReference.java
@@ -4,6 +4,7 @@ public class MessageReference {
private String href;
private int totalCount;
+ private String lastMessageId;
public String getHREF() {
return href;
@@ -21,11 +22,16 @@ public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
+ public String getLastMessageId() {
+ return lastMessageId;
+ }
+
@Override
public String toString() {
return "MessageReference{" +
"href='" + href + '\'' +
", totalCount=" + totalCount +
+ ", lastMessageId='" + lastMessageId + '\'' +
'}';
}
}
diff --git a/api/src/main/java/com/messagebird/objects/MessageResponse.java b/api/src/main/java/com/messagebird/objects/MessageResponse.java
index be132d4f..aa104e52 100644
--- a/api/src/main/java/com/messagebird/objects/MessageResponse.java
+++ b/api/src/main/java/com/messagebird/objects/MessageResponse.java
@@ -1,6 +1,9 @@
package com.messagebird.objects;
+import org.jetbrains.annotations.Nullable;
+
import java.io.Serializable;
+import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.List;
@@ -194,7 +197,7 @@ public Map getTypeDetails() {
static public class Recipients implements Serializable {
private static final long serialVersionUID = 547164972757802213L;
-
+ private Integer totalCount;
private Integer totalSentCount;
private Integer totalDeliveredCount;
private Integer totalDeliveryFailedCount;
@@ -206,13 +209,18 @@ public Recipients() {
@Override
public String toString() {
return "Recipients{" +
- "totalSentCount=" + totalSentCount +
+ "totalCount=" + totalCount +
+ ", totalSentCount=" + totalSentCount +
", totalDeliveredCount=" + totalDeliveredCount +
", totalDeliveryFailedCount=" + totalDeliveryFailedCount +
", items=" + items +
'}';
}
+ public Integer getTotalCount() {
+ return totalCount;
+ }
+
/**
* The count of recipients that have the message pending (status sent, and buffered).
*
@@ -258,8 +266,20 @@ static public class Items implements Serializable {
private static final long serialVersionUID = -4104837036540050532L;
private BigInteger recipient;
+ private BigInteger originator;
private String status;
private Date statusDatetime;
+ private String recipientCountry;
+ private Integer recipientCountryPrefix;
+ private String recipientOperator;
+ private Integer messageLength;
+ private String statusReason;
+ @Nullable
+ private Price price;
+ private String mccmnc;
+ private String mcc;
+ private String mnc;
+ private int messagePartCount;
public Items() {
}
@@ -268,9 +288,20 @@ public Items() {
public String toString() {
return "Items{" +
"recipient=" + recipient +
+ ", originator=" + originator +
", status='" + status + '\'' +
", statusDatetime=" + statusDatetime +
- "}";
+ ", recipientCountry='" + recipientCountry + '\'' +
+ ", recipientCountryPrefix=" + recipientCountryPrefix +
+ ", recipientOperator='" + recipientOperator + '\'' +
+ ", messageLength=" + messageLength +
+ ", statusReason='" + statusReason + '\'' +
+ ", price=" + price +
+ ", mccmnc='" + mccmnc + '\'' +
+ ", mcc='" + mcc + '\'' +
+ ", mnc='" + mnc + '\'' +
+ ", messagePartCount=" + messagePartCount +
+ '}';
}
/**
@@ -300,6 +331,84 @@ public Date getStatusDatetime() {
return statusDatetime;
}
+ public Price getPrice() {
+ return price;
+ }
+
+ public BigInteger getOriginator() {
+ return originator;
+ }
+
+ public String getRecipientCountry() {
+ return recipientCountry;
+ }
+
+ public Integer getRecipientCountryPrefix() {
+ return recipientCountryPrefix;
+ }
+
+ public String getRecipientOperator() {
+ return recipientOperator;
+ }
+
+ public Integer getMessageLength() {
+ return messageLength;
+ }
+
+ public String getStatusReason() {
+ return statusReason;
+ }
+
+ public String getMccmnc() {
+ return mccmnc;
+ }
+
+ public String getMcc() {
+ return mcc;
+ }
+
+ public String getMnc() {
+ return mnc;
+ }
+
+ public int getMessagePartCount() {
+ return messagePartCount;
+ }
+ }
+
+ /**
+ * Response price of items
+ */
+ static public class Price implements Serializable {
+
+ private static final long serialVersionUID = -4104837036540050532L;
+
+ private BigDecimal amount;
+ private String currency;
+
+ public Price() {
+ }
+
+ @Override
+ public String toString() {
+ return "Price{" +
+ "amount=" + amount +
+ ", currency=" + currency +
+ "}";
+ }
+
+ public float getAmount() {
+ return amount.floatValue();
+ }
+
+ public BigDecimal getAmountDecimal() {
+ return amount;
+ }
+
+ public String getCurrency() {
+ return currency;
+ }
+
}
}
diff --git a/api/src/main/java/com/messagebird/objects/MsgType.java b/api/src/main/java/com/messagebird/objects/MsgType.java
index dc9af724..bfc421c8 100644
--- a/api/src/main/java/com/messagebird/objects/MsgType.java
+++ b/api/src/main/java/com/messagebird/objects/MsgType.java
@@ -7,6 +7,7 @@
*/
public enum MsgType {
sms("sms"),
+ mms("mms"),
binary("binary"),
premium("premium"),
flash("flash");
diff --git a/api/src/main/java/com/messagebird/objects/OutboundSmsPrice.java b/api/src/main/java/com/messagebird/objects/OutboundSmsPrice.java
new file mode 100644
index 00000000..3733cf69
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/OutboundSmsPrice.java
@@ -0,0 +1,60 @@
+package com.messagebird.objects;
+
+import java.math.BigDecimal;
+
+public class OutboundSmsPrice {
+ private BigDecimal price;
+ private String currencyCode;
+ private String mccmnc;
+ private String mcc;
+ private String mnc;
+ private String countryName;
+ private String countryIsoCode;
+ private String operatorName;
+
+ public BigDecimal getPrice() {
+ return price;
+ }
+
+ public String getCurrencyCode() {
+ return currencyCode;
+ }
+
+ public String getMccmnc() {
+ return mccmnc;
+ }
+
+ public String getMcc() {
+ return mcc;
+ }
+
+ public String getMnc() {
+ return mnc;
+ }
+
+ public String getCountryName() {
+ return countryName;
+ }
+
+ public String getCountryIsoCode() {
+ return countryIsoCode;
+ }
+
+ public String getOperatorName() {
+ return operatorName;
+ }
+
+ @Override
+ public String toString() {
+ return "OutboundSmsPrice{" +
+ "price=" + price +
+ ", currencyCode='" + currencyCode + '\'' +
+ ", mccmnc='" + mccmnc + '\'' +
+ ", mcc='" + mcc + '\'' +
+ ", mnc='" + mnc + '\'' +
+ ", countryName='" + countryName + '\'' +
+ ", countryIsoCode='" + countryIsoCode + '\'' +
+ ", operatorName='" + operatorName + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/OutboundSmsPriceResponse.java b/api/src/main/java/com/messagebird/objects/OutboundSmsPriceResponse.java
new file mode 100644
index 00000000..6d036882
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/OutboundSmsPriceResponse.java
@@ -0,0 +1,36 @@
+package com.messagebird.objects;
+
+import java.util.List;
+
+public class OutboundSmsPriceResponse {
+ private int gateway;
+ private String currencyCode;
+ private int totalCount;
+ private List prices;
+
+ public int getGateway() {
+ return gateway;
+ }
+
+ public String getCurrencyCode() {
+ return currencyCode;
+ }
+
+ public int getTotalCount() {
+ return totalCount;
+ }
+
+ public List getPrices() {
+ return prices;
+ }
+
+ @Override
+ public String toString() {
+ return "OutboundSmsPriceResponse{" +
+ "gateway=" + gateway +
+ ", currencyCode='" + currencyCode + '\'' +
+ ", totalCount=" + totalCount +
+ ", prices=" + prices +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/PhoneNumber.java b/api/src/main/java/com/messagebird/objects/PhoneNumber.java
new file mode 100644
index 00000000..5fdda19f
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/PhoneNumber.java
@@ -0,0 +1,50 @@
+package com.messagebird.objects;
+
+import com.messagebird.objects.PhoneNumberFeature;
+
+import java.util.EnumSet;
+
+public class PhoneNumber {
+ private String number;
+ private String country;
+ private String region;
+ private String locality;
+ private EnumSet features;
+ private String type;
+
+ public String getNumber() {
+ return this.number;
+ }
+
+ public String getCountry() {
+ return this.country;
+ }
+
+ public String getRegion() {
+ return this.region;
+ }
+
+ public String getLocality() {
+ return this.locality;
+ }
+
+ public EnumSet getFeatures() {
+ return this.features;
+ }
+
+ public String getType() {
+ return this.type;
+ }
+
+ @Override
+ public String toString() {
+ return "PhoneNumber{" +
+ "number='" + number + "\'" +
+ ", country='" + country + "\'" +
+ ", region='" + region + "\'" +
+ ", locality='" + locality + "\'" +
+ ", features=" + features +
+ ", type='" + type + "\'" +
+ "}";
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/PhoneNumberFeature.java b/api/src/main/java/com/messagebird/objects/PhoneNumberFeature.java
new file mode 100644
index 00000000..790177c9
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/PhoneNumberFeature.java
@@ -0,0 +1,19 @@
+package com.messagebird.objects;
+
+public enum PhoneNumberFeature {
+
+ SMS("sms"),
+ MMS("mms"),
+ VOICE("voice");
+
+ private String type;
+
+ PhoneNumberFeature(String type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return this.type;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/PhoneNumberSearchPattern.java b/api/src/main/java/com/messagebird/objects/PhoneNumberSearchPattern.java
new file mode 100644
index 00000000..cf0b3099
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/PhoneNumberSearchPattern.java
@@ -0,0 +1,18 @@
+package com.messagebird.objects;
+
+public enum PhoneNumberSearchPattern {
+ START("start"),
+ ANYWHERE("anywhere"),
+ END("end");
+
+ private String type;
+
+ PhoneNumberSearchPattern(String type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return this.type;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/PhoneNumberType.java b/api/src/main/java/com/messagebird/objects/PhoneNumberType.java
new file mode 100644
index 00000000..82908203
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/PhoneNumberType.java
@@ -0,0 +1,18 @@
+package com.messagebird.objects;
+
+public enum PhoneNumberType {
+ LANDLINE("landline"),
+ MOBILE("mobile"),
+ PREMIUM_RATE("premium_rate");
+
+ private String type;
+
+ PhoneNumberType(String type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return this.type;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/PhoneNumbersLookup.java b/api/src/main/java/com/messagebird/objects/PhoneNumbersLookup.java
new file mode 100644
index 00000000..8b8396fa
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/PhoneNumbersLookup.java
@@ -0,0 +1,93 @@
+package com.messagebird.objects;
+
+import com.messagebird.exceptions.GeneralException;
+import com.messagebird.objects.PhoneNumberFeature;
+import com.messagebird.objects.PhoneNumberType;
+import com.messagebird.objects.PhoneNumberSearchPattern;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.lang.reflect.Field;
+
+public class PhoneNumbersLookup {
+
+ private Number number;
+ private Number limit;
+ private EnumSet features;
+ private PhoneNumberType type;
+ private PhoneNumberSearchPattern searchPattern;
+
+ public Number getNumber() {
+ return this.number;
+ }
+
+ public EnumSet getFeatures() {
+ return this.features;
+ }
+
+ public PhoneNumberType getType() {
+ return this.type;
+ }
+
+ public Number getLimit() {
+ return this.limit;
+ }
+
+ public PhoneNumberSearchPattern getSearchPattern() {
+ return this.searchPattern;
+ }
+
+ public void setNumber(Number number) {
+ this.number = number;
+ }
+
+ public void setFeatures(EnumSet features) {
+ this.features = features;
+ }
+
+ public void setFeatures(PhoneNumberFeature... features) {
+ EnumSet featuresEnum = EnumSet.noneOf(PhoneNumberFeature.class);
+ featuresEnum.addAll(Arrays.asList(features));
+ this.features = featuresEnum;
+ }
+
+ public void setType(PhoneNumberType type) {
+ this.type = type;
+ }
+
+ public void setLimit(Number limit) {
+ this.limit = limit;
+ }
+
+ public void setSearchPattern(PhoneNumberSearchPattern searchPattern) {
+ this.searchPattern = searchPattern;
+ }
+
+ public HashMap toHashMap() throws GeneralException {
+ final HashMap map = new HashMap();
+ for (Field f: getClass().getDeclaredFields()) {
+ try {
+ Object value = f.get(this);
+ String key = f.getName();
+ if (value != null) {
+ map.put(key, value);
+ }
+ } catch (IllegalAccessException exception) {
+ throw new GeneralException("Error Converting PhoneNumbersLookup Class to HashMap.");
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public String toString() {
+ return "PhoneNumbersLookup{" +
+ " number='" + number + "'" +
+ ", features='" + features + "'" +
+ ", type='" + type + "'" +
+ ", limit='" + limit + "'" +
+ ", searchPattern='" + searchPattern + "'" +
+ "}";
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/PhoneNumbersResponse.java b/api/src/main/java/com/messagebird/objects/PhoneNumbersResponse.java
new file mode 100644
index 00000000..68e9ef4d
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/PhoneNumbersResponse.java
@@ -0,0 +1,38 @@
+package com.messagebird.objects;
+
+import com.messagebird.objects.PhoneNumber;
+
+import java.util.List;
+
+import java.io.Serializable;
+
+public class PhoneNumbersResponse implements Serializable {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 6177098534499444839L;
+ private Number limit;
+ private Number offset;
+ private List items;
+
+ public Number getLimit() {
+ return this.limit;
+ }
+
+ public Number getOffset() {
+ return this.offset;
+ }
+
+ public List getItems() {
+ return this.items;
+ }
+
+ @Override
+ public String toString() {
+ return "PhoneNumbersResponse{" +
+ "limit=" + limit +
+ ", offset=" + offset +
+ ", items=" + items +
+ "}";
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/PurchasedNumber.java b/api/src/main/java/com/messagebird/objects/PurchasedNumber.java
new file mode 100644
index 00000000..fb07a686
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/PurchasedNumber.java
@@ -0,0 +1,30 @@
+package com.messagebird.objects;
+
+import java.util.List;
+
+public class PurchasedNumber extends PhoneNumber {
+ private List tags;
+ private String status;
+
+ public List getTags() {
+ return tags;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ @Override
+ public String toString() {
+ return "PhoneNumber{" +
+ "number='" + this.getNumber() + "\'" +
+ ", country='" + this.getCountry() + "\'" +
+ ", region='" + this.getRegion() + "\'" +
+ ", locality='" + this.getLocality() + "\'" +
+ ", features=" + this.getFeatures() +
+ ", type='" + this.getType() + "\'" +
+ ", tags='" + tags + "\'" +
+ ", status='" + status + "\'" +
+ "}";
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/PurchasedNumberCreatedResponse.java b/api/src/main/java/com/messagebird/objects/PurchasedNumberCreatedResponse.java
new file mode 100644
index 00000000..8922db2e
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/PurchasedNumberCreatedResponse.java
@@ -0,0 +1,32 @@
+package com.messagebird.objects;
+
+import java.util.Date;
+
+public class PurchasedNumberCreatedResponse extends PurchasedNumber {
+ private Date createdAt;
+ private Date renewalAt;
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public Date getRenewalAt() {
+ return renewalAt;
+ }
+
+ @Override
+ public String toString() {
+ return "PhoneNumber{" +
+ "number='" + this.getNumber() + "\'" +
+ ", country='" + this.getCountry() + "\'" +
+ ", region='" + this.getRegion() + "\'" +
+ ", locality='" + this.getLocality() + "\'" +
+ ", features=" + this.getFeatures() +
+ ", type='" + this.getType() + "\'" +
+ ", tags='" + this.getTags() + "\'" +
+ ", status='" + this.getStatus() + "\'" +
+ ", createdAt='" + createdAt + "\'" +
+ ", renewalAt='" + renewalAt + "\'" +
+ "}";
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/PurchasedNumbersFilter.java b/api/src/main/java/com/messagebird/objects/PurchasedNumbersFilter.java
new file mode 100644
index 00000000..40c6dcd9
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/PurchasedNumbersFilter.java
@@ -0,0 +1,135 @@
+package com.messagebird.objects;
+
+import com.messagebird.exceptions.GeneralException;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+
+public class PurchasedNumbersFilter implements Serializable {
+ private int limit = 10;
+ private int offset = 0;
+ private EnumSet features = EnumSet.noneOf(PhoneNumberFeature.class);
+ private ArrayList tags = new ArrayList<>();
+ private String number;
+ private String region;
+ private String locality;
+ private PhoneNumberType type;
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public void setLimit(int limit) {
+ this.limit = limit;
+ }
+
+ public int getOffset() {
+ return offset;
+ }
+
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ public EnumSet getFeatures() {
+ return features;
+ }
+
+ public void addFeature(PhoneNumberFeature... features) {
+ Collections.addAll(this.features, features);
+ }
+
+ public void removeFeature(PhoneNumberFeature... features) {
+ for (PhoneNumberFeature feature: features) {
+ this.features.remove(feature);
+ }
+ }
+
+ public ArrayList getTags() {
+ return tags;
+ }
+
+ public void addTag(String... tags) {
+ for (String tag: tags) {
+ if (!this.tags.contains(tag)) {
+ this.tags.add(tag);
+ }
+ }
+ }
+
+ public void removeTag(String... tags) {
+ for (String tag: tags) {
+ this.tags.remove(tag);
+ }
+ }
+
+ public void clearTags() {
+ this.tags.clear();
+ }
+
+ public String getNumber() {
+ return number;
+ }
+
+ public void setNumber(String number) {
+ this.number = number;
+ }
+
+ public String getRegion() {
+ return region;
+ }
+
+ public void setRegion(String region) {
+ this.region = region;
+ }
+
+ public String getLocality() {
+ return locality;
+ }
+
+ public void setLocality(String locality) {
+ this.locality = locality;
+ }
+
+ public PhoneNumberType getType() {
+ return type;
+ }
+
+ public void setType(PhoneNumberType type) {
+ this.type = type;
+ }
+
+ public HashMap toHashMap() throws GeneralException {
+ final HashMap map = new HashMap();
+ for (Field f: getClass().getDeclaredFields()) {
+ try {
+ Object value = f.get(this);
+ String key = f.getName();
+ if (value != null) {
+ map.put(key, value);
+ }
+ } catch (IllegalAccessException exception) {
+ throw new GeneralException("Error converting to HashMap.");
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public String toString() {
+ return "PurchasedNumbersFilter{" +
+ "limit=" + limit +
+ ", offset=" + offset +
+ ", features=" + features +
+ ", tags=" + tags +
+ ", number='" + number + '\'' +
+ ", region='" + region + '\'' +
+ ", locality='" + locality + '\'' +
+ ", type=" + type +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/PurchasedNumbersResponse.java b/api/src/main/java/com/messagebird/objects/PurchasedNumbersResponse.java
new file mode 100644
index 00000000..b61928ca
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/PurchasedNumbersResponse.java
@@ -0,0 +1,42 @@
+package com.messagebird.objects;
+
+import java.util.List;
+
+public class PurchasedNumbersResponse {
+ private int offset;
+ private int limit;
+ private int count;
+ private int totalCount;
+ private List items;
+
+ public int getOffset() {
+ return offset;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public int getTotalCount() {
+ return totalCount;
+ }
+
+ public List getItems() {
+ return items;
+ }
+
+ @Override
+ public String toString() {
+ return "PurchasedNumbersResponse{" +
+ "offset=" + offset +
+ ", limit=" + limit +
+ ", count=" + count +
+ ", totalCount=" + totalCount +
+ ", items=" + items +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/VerifyMessage.java b/api/src/main/java/com/messagebird/objects/VerifyMessage.java
new file mode 100644
index 00000000..b2929f11
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/VerifyMessage.java
@@ -0,0 +1,35 @@
+package com.messagebird.objects;
+
+import java.io.Serializable;
+
+/**
+ * Created by leandro.pinto on 22/06/15.
+ */
+public class VerifyMessage implements Serializable {
+
+ private String id;
+ private String status;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String toString() {
+ return "VerifyMessage {" + " " +
+ "id=" + this.id + " " +
+ "status=" + this.status + " " +
+ "}";
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/VerifyRequest.java b/api/src/main/java/com/messagebird/objects/VerifyRequest.java
index 45a6b194..bee684e9 100644
--- a/api/src/main/java/com/messagebird/objects/VerifyRequest.java
+++ b/api/src/main/java/com/messagebird/objects/VerifyRequest.java
@@ -15,8 +15,10 @@ public class VerifyRequest implements Serializable {
private String template;
private Integer timeout;
private Integer tokenLength;
+ private Integer maxAttempts;
private Gender voice;
private Language language;
+ private String subject;
public VerifyRequest(String recipient) {
this.recipient = recipient;
@@ -54,6 +56,10 @@ public void setType(VerifyType type) {
this.type = type;
}
+ public void setType(String type) {
+ this.type = VerifyType.valueOf(type.toUpperCase());
+ }
+
/**
* The datacoding used by the template.
*
@@ -112,4 +118,19 @@ public void setLanguage(Language language) {
this.language = language;
}
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public Integer getMaxAttempts() {
+ return maxAttempts;
+ }
+
+ public void setMaxAttempts(Integer maxAttempts) {
+ this.maxAttempts = maxAttempts;
+ }
}
diff --git a/api/src/main/java/com/messagebird/objects/VerifyType.java b/api/src/main/java/com/messagebird/objects/VerifyType.java
index 8965e7a2..630e8e53 100644
--- a/api/src/main/java/com/messagebird/objects/VerifyType.java
+++ b/api/src/main/java/com/messagebird/objects/VerifyType.java
@@ -9,7 +9,8 @@ public enum VerifyType {
FLASH("flash"),
SMS("sms"),
- TTS("tts");
+ TTS("tts"),
+ EMAIL("email");
final String value;
diff --git a/api/src/main/java/com/messagebird/objects/VoiceStepOption.java b/api/src/main/java/com/messagebird/objects/VoiceStepOption.java
index 9e8e1292..70c0670b 100644
--- a/api/src/main/java/com/messagebird/objects/VoiceStepOption.java
+++ b/api/src/main/java/com/messagebird/objects/VoiceStepOption.java
@@ -24,6 +24,9 @@ public class VoiceStepOption implements Serializable {
private int machineTimeout;
private String onFinish;
private boolean mask;
+ private String keys;
+ private int duration;
+ private int interval;
public String getDestination() {
return destination;
@@ -169,6 +172,18 @@ public void setMask(boolean mask) {
this.mask = mask;
}
+ public String getKeys() { return keys; }
+
+ public void setKeys(String keys) { this.keys = keys; }
+
+ public int getDuration() { return duration; }
+
+ public void setDuration(int duration) { this.duration = duration; }
+
+ public int getInterval() { return interval; }
+
+ public void setInterval(int interval) { this.interval = interval; }
+
@Override
public String toString() {
return "VoiceStepOption{" +
@@ -189,7 +204,10 @@ public String toString() {
", ifMachine='" + ifMachine + '\'' +
", machineTimeout=" + machineTimeout +
", onFinish='" + onFinish + '\'' +
- ", mask=" + mask +
+ ", mask=" + mask + '\'' +
+ ", keys='" + keys + '\'' +
+ ", interval='" + interval + '\'' +
+ ", duration='" + duration +
'}';
}
}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationChannel.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationChannel.java
index 32db29e6..2c2bd06e 100644
--- a/api/src/main/java/com/messagebird/objects/conversations/ConversationChannel.java
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationChannel.java
@@ -11,6 +11,8 @@ public class ConversationChannel {
private String id;
private String name;
+ // See: ConversationPlatformConstants
+ private String platformId;
private ConversationChannelStatus status;
private Date createdDatetime;
private Date updatedDatetime;
@@ -55,11 +57,20 @@ public void setUpdatedDatetime(final Date updatedDatetime) {
this.updatedDatetime = updatedDatetime;
}
+ public String getPlatformId() {
+ return platformId;
+ }
+
+ public void setPlatformId(String platformId) {
+ this.platformId = platformId;
+ }
+
@Override
public String toString() {
return "ConversationChannel{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
+ ", platformId=" + platformId +
", status=" + status +
", createdDatetime=" + createdDatetime +
", updatedDatetime=" + updatedDatetime +
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationContent.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationContent.java
index 2edfb5b3..8b4fe164 100644
--- a/api/src/main/java/com/messagebird/objects/conversations/ConversationContent.java
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationContent.java
@@ -1,9 +1,16 @@
package com.messagebird.objects.conversations;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
/**
* ConversationContent wraps actual content. The field that should be set here
* is indicated by ConversationContentType.
+ *
+ *
Unknown keys are ignored on deserialization so that consumers parsing
+ * webhook payloads in their own handlers are not broken by content fields added
+ * after their SDK version. Serialization is unaffected.
*/
+@JsonIgnoreProperties(ignoreUnknown = true)
public class ConversationContent {
private ConversationContentMedia audio;
@@ -11,6 +18,7 @@ public class ConversationContent {
private ConversationContentHsm hsm;
private ConversationContentMedia image;
private ConversationContentLocation location;
+ private ConversationContentEmail email;
private String text;
private ConversationContentMedia video;
@@ -70,6 +78,14 @@ public void setVideo(ConversationContentMedia video) {
this.video = video;
}
+ public ConversationContentEmail getEmail() {
+ return email;
+ }
+
+ public void setEmail(ConversationContentEmail email) {
+ this.email = email;
+ }
+
@Override
public String toString() {
return "ConversationContent{" +
@@ -78,6 +94,7 @@ public String toString() {
", hsm=" + hsm +
", image=" + image +
", location=" + location +
+ ", email=" + email +
", text='" + text + '\'' +
", video=" + video +
'}';
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationContentEmail.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationContentEmail.java
new file mode 100644
index 00000000..3ba8b2d8
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationContentEmail.java
@@ -0,0 +1,133 @@
+package com.messagebird.objects.conversations;
+
+import java.util.List;
+import java.util.Map;
+
+public class ConversationContentEmail {
+ private String id;
+ private ConversationEmailRecipient from;
+ private List to;
+ private String subject;
+ private ConversationEmailContent content;
+ private String replyTo;
+ private String returnPath;
+ private Map headers;
+ private ConversationEmailTracking tracking;
+ private boolean performSubstitutions;
+ private List attachments;
+ private List inlineImages;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public ConversationEmailRecipient getFrom() {
+ return from;
+ }
+
+ public void setFrom(ConversationEmailRecipient from) {
+ this.from = from;
+ }
+
+ public List getTo() {
+ return to;
+ }
+
+ public void setTo(List to) {
+ this.to = to;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ public ConversationEmailContent getContent() {
+ return content;
+ }
+
+ public void setContent(ConversationEmailContent content) {
+ this.content = content;
+ }
+
+ public String getReplyTo() {
+ return replyTo;
+ }
+
+ public void setReplyTo(String replyTo) {
+ this.replyTo = replyTo;
+ }
+
+ public String getReturnPath() {
+ return returnPath;
+ }
+
+ public void setReturnPath(String returnPath) {
+ this.returnPath = returnPath;
+ }
+
+ public Map getHeaders() {
+ return headers;
+ }
+
+ public void setHeaders(Map headers) {
+ this.headers = headers;
+ }
+
+ public ConversationEmailTracking getTracking() {
+ return tracking;
+ }
+
+ public void setTracking(ConversationEmailTracking tracking) {
+ this.tracking = tracking;
+ }
+
+ public boolean isPerformSubstitutions() {
+ return performSubstitutions;
+ }
+
+ public void setPerformSubstitutions(boolean performSubstitutions) {
+ this.performSubstitutions = performSubstitutions;
+ }
+
+ public List getAttachments() {
+ return attachments;
+ }
+
+ public void setAttachments(List attachments) {
+ this.attachments = attachments;
+ }
+
+ public List getInlineImages() {
+ return inlineImages;
+ }
+
+ public void setInlineImages(List inlineImages) {
+ this.inlineImages = inlineImages;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationContentEmail{" +
+ "id='" + id + '\'' +
+ ", from=" + from +
+ ", to=" + to +
+ ", subject='" + subject + '\'' +
+ ", content=" + content +
+ ", replyTo='" + replyTo + '\'' +
+ ", returnPath='" + returnPath + '\'' +
+ ", headers=" + headers +
+ ", tracking=" + tracking +
+ ", performSubstitutions=" + performSubstitutions +
+ ", attachments=" + attachments +
+ ", inlineImages=" + inlineImages +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationContentHsm.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationContentHsm.java
index 3da74c2c..824f7fc8 100644
--- a/api/src/main/java/com/messagebird/objects/conversations/ConversationContentHsm.java
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationContentHsm.java
@@ -18,17 +18,20 @@ public class ConversationContentHsm {
private String templateName;
private ConversationHsmLanguage language;
private List params;
+ private List components;
public ConversationContentHsm(
final String namespace,
final String templateName,
final ConversationHsmLanguage language,
- final List params
+ final List params,
+ final List components
) {
this.namespace = namespace;
this.templateName = templateName;
this.language = language;
this.params = params;
+ this.components = components;
}
public ConversationContentHsm(
@@ -85,6 +88,14 @@ public void setParams(final List params) {
this.params = params;
}
+ public List getComponents() {
+ return components;
+ }
+
+ public void setComponents(List components) {
+ this.components = components;
+ }
+
@Override
public String toString() {
return "ConversationContentHsm{" +
@@ -92,6 +103,7 @@ public String toString() {
", templateName='" + templateName + '\'' +
", language=" + language +
", params=" + params +
+ ", components=" + components +
'}';
}
}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationContentMedia.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationContentMedia.java
index b2d1e4ca..7ac29e8f 100644
--- a/api/src/main/java/com/messagebird/objects/conversations/ConversationContentMedia.java
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationContentMedia.java
@@ -7,6 +7,12 @@
public class ConversationContentMedia {
private String url;
+ private String caption;
+
+ public ConversationContentMedia(final String url, final String caption) {
+ this.url = url;
+ this.caption = caption;
+ }
public ConversationContentMedia(final String url) {
this.url = url;
@@ -24,10 +30,19 @@ public void setUrl(final String url) {
this.url = url;
}
+ public String getCaption() {
+ return caption;
+ }
+
+ public void setCaption(String caption) {
+ this.caption = caption;
+ }
+
@Override
public String toString() {
return "ConversationContentMedia{" +
"url='" + url + '\'' +
+ ", caption='" + caption + '\'' +
'}';
}
}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationContentType.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationContentType.java
index 93f6bb4f..3dec398a 100644
--- a/api/src/main/java/com/messagebird/objects/conversations/ConversationContentType.java
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationContentType.java
@@ -15,7 +15,8 @@ public enum ConversationContentType {
IMAGE("image"),
LOCATION("location"),
TEXT("text"),
- VIDEO("video");
+ VIDEO("video"),
+ EMAIL("email");
private final String type;
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailAttachment.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailAttachment.java
new file mode 100644
index 00000000..82e1a48a
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailAttachment.java
@@ -0,0 +1,60 @@
+package com.messagebird.objects.conversations;
+
+public class ConversationEmailAttachment {
+ private String id;
+ private String name;
+ private String type;
+ private String URL;
+ private String length;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getURL() {
+ return URL;
+ }
+
+ public void setURL(String URL) {
+ this.URL = URL;
+ }
+
+ public String getLength() {
+ return length;
+ }
+
+ public void setLength(String length) {
+ this.length = length;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationEmailAttachment{" +
+ "id='" + id + '\'' +
+ ", name='" + name + '\'' +
+ ", type='" + type + '\'' +
+ ", URL='" + URL + '\'' +
+ ", length='" + length + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailContent.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailContent.java
new file mode 100644
index 00000000..7127b104
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailContent.java
@@ -0,0 +1,30 @@
+package com.messagebird.objects.conversations;
+
+public class ConversationEmailContent {
+ private String html;
+ private String text;
+
+ public String getHtml() {
+ return html;
+ }
+
+ public void setHtml(String html) {
+ this.html = html;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationEmailContent{" +
+ "html='" + html + '\'' +
+ ", text='" + text + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailInlineImage.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailInlineImage.java
new file mode 100644
index 00000000..a7318216
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailInlineImage.java
@@ -0,0 +1,70 @@
+package com.messagebird.objects.conversations;
+
+public class ConversationEmailInlineImage {
+ private String id;
+ private String name;
+ private String type;
+ private String URL;
+ private int length;
+ private String contentId;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getURL() {
+ return URL;
+ }
+
+ public void setURL(String URL) {
+ this.URL = URL;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public void setLength(int length) {
+ this.length = length;
+ }
+
+ public String getContentId() {
+ return contentId;
+ }
+
+ public void setContentId(String contentId) {
+ this.contentId = contentId;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationEmailInlineImage{" +
+ "id='" + id + '\'' +
+ ", name='" + name + '\'' +
+ ", type='" + type + '\'' +
+ ", URL='" + URL + '\'' +
+ ", length=" + length +
+ ", contentId='" + contentId + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailRecipient.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailRecipient.java
new file mode 100644
index 00000000..f5bbdfb9
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailRecipient.java
@@ -0,0 +1,42 @@
+package com.messagebird.objects.conversations;
+
+import java.util.Map;
+
+public class ConversationEmailRecipient {
+ private String address;
+ private String name;
+ private Map variables;
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Map getVariables() {
+ return variables;
+ }
+
+ public void setVariables(Map variables) {
+ this.variables = variables;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationEmailRecipient{" +
+ "address='" + address + '\'' +
+ ", name='" + name + '\'' +
+ ", variables=" + variables +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailTracking.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailTracking.java
new file mode 100644
index 00000000..b8861d14
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationEmailTracking.java
@@ -0,0 +1,30 @@
+package com.messagebird.objects.conversations;
+
+public class ConversationEmailTracking {
+ private boolean open;
+ private boolean click;
+
+ public boolean isOpen() {
+ return open;
+ }
+
+ public void setOpen(boolean open) {
+ this.open = open;
+ }
+
+ public boolean isClick() {
+ return click;
+ }
+
+ public void setClick(boolean click) {
+ this.click = click;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationEmailTracking{" +
+ "open=" + open +
+ ", click=" + click +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationFallbackOption.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationFallbackOption.java
new file mode 100644
index 00000000..0048de12
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationFallbackOption.java
@@ -0,0 +1,38 @@
+package com.messagebird.objects.conversations;
+
+public class ConversationFallbackOption {
+ private String from;
+ private String after;
+
+ public ConversationFallbackOption() {
+ }
+
+ public ConversationFallbackOption(String from, String after) {
+ this.from = from;
+ this.after = after;
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ public String getAfter() {
+ return after;
+ }
+
+ public void setAfter(String after) {
+ this.after = after;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationFallbackOption{" +
+ "from='" + from + '\'' +
+ ", after='" + after + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationHsmLocalizableParameterCurrency.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationHsmLocalizableParameterCurrency.java
index b17ecbce..708938ee 100644
--- a/api/src/main/java/com/messagebird/objects/conversations/ConversationHsmLocalizableParameterCurrency.java
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationHsmLocalizableParameterCurrency.java
@@ -11,10 +11,10 @@ public class ConversationHsmLocalizableParameterCurrency {
/**
* Instantiates a localizable parameter for currencies.
*
- * @param code ISO 4217 compliant currency code.
+ * @param currencyCode ISO 4217 compliant currency code.
* @param amount Amount multiplied by 1000. E.g. 12.34 becomes 12340.
*/
- public ConversationHsmLocalizableParameterCurrency(final String code, final int amount) {
+ public ConversationHsmLocalizableParameterCurrency(final String currencyCode, final int amount) {
this.currencyCode = currencyCode;
this.amount = amount;
}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java
index 43b2c275..1dea8280 100644
--- a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessage.java
@@ -1,6 +1,7 @@
package com.messagebird.objects.conversations;
import java.util.Date;
+import java.util.Map;
/**
* Response object that represents a conversation's message. Messages can be
@@ -12,12 +13,20 @@ public class ConversationMessage {
private String id;
private String conversationId;
private String channelId;
+ private String trackId;
private ConversationMessageDirection direction;
private ConversationMessageStatus status;
private ConversationContentType type;
private ConversationContent content;
private Date createdDatetime;
private Date updatedDatetime;
+ private Map source;
+ private ConversationMessageTag tag;
+ private ConversationMessageMetadata metadata;
+ /**
+ * See: {@link ConversationPlatformConstants}
+ */
+ private String platform;
public String getId() {
return id;
@@ -91,6 +100,46 @@ public void setUpdatedDatetime(Date updatedDatetime) {
this.updatedDatetime = updatedDatetime;
}
+ public Map getSource() {
+ return source;
+ }
+
+ public void setSource(Map source) {
+ this.source = source;
+ }
+
+ public ConversationMessageTag getTag() {
+ return tag;
+ }
+
+ public void setTag(ConversationMessageTag tag) {
+ this.tag = tag;
+ }
+
+ public ConversationMessageMetadata getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(ConversationMessageMetadata metadata) {
+ this.metadata = metadata;
+ }
+
+ public String getPlatform() {
+ return platform;
+ }
+
+ public void setPlatform(String platform) {
+ this.platform = platform;
+ }
+
+ public String getTrackId() {
+ return trackId;
+ }
+
+ public void setTrackId(String trackId) {
+ this.trackId = trackId;
+ }
+
@Override
public String toString() {
return "ConversationMessage{" +
@@ -100,9 +149,14 @@ public String toString() {
", direction=" + direction +
", status=" + status +
", type=" + type +
+ ", trackID=" + trackId +
", content=" + content +
", createdDatetime=" + createdDatetime +
", updatedDatetime=" + updatedDatetime +
+ ", source=" + source +
+ ", tag=" + tag +
+ ", metadata=" + metadata +
+ ", platform='" + platform + '\'' +
'}';
}
-}
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageMetadata.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageMetadata.java
new file mode 100644
index 00000000..c41e814b
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageMetadata.java
@@ -0,0 +1,42 @@
+package com.messagebird.objects.conversations;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import java.util.Date;
+
+/**
+ * Inner metadata attached to a conversation message. Present on both incoming
+ * messages and status webhook payloads. {@code sender.userId} always contains
+ * the BSUID when Meta provides one. When both identifiers exist, the phone
+ * number appears in the parent {@code from} field, not in this object.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ConversationMessageMetadata {
+
+ private ConversationSenderMetadata sender;
+ private Date receivedAt;
+
+ public ConversationSenderMetadata getSender() {
+ return sender;
+ }
+
+ public void setSender(ConversationSenderMetadata sender) {
+ this.sender = sender;
+ }
+
+ public Date getReceivedAt() {
+ return receivedAt;
+ }
+
+ public void setReceivedAt(Date receivedAt) {
+ this.receivedAt = receivedAt;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationMessageMetadata{" +
+ "sender=" + sender +
+ ", receivedAt=" + receivedAt +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageRequest.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageRequest.java
index 5607c147..420213e3 100644
--- a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageRequest.java
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageRequest.java
@@ -1,5 +1,7 @@
package com.messagebird.objects.conversations;
+import java.util.Map;
+
/**
* Request object that is used to send new messages over a channel.
*/
@@ -8,6 +10,10 @@ public class ConversationMessageRequest {
private ConversationContentType type;
private ConversationContent content;
private String channelId;
+ private String reportUrl;
+ private String trackId;
+ private String ttl;
+ private Map source;
public ConversationContentType getType() {
return type;
@@ -33,12 +39,47 @@ public void setChannelId(String channelId) {
this.channelId = channelId;
}
+ public String getReportUrl() {
+ return reportUrl;
+ }
+
+ public void setReportUrl(String reportUrl) {
+ this.reportUrl = reportUrl;
+ }
+
+ public Map getSource() {
+ return source;
+ }
+
+ public void setSource(Map source) {
+ this.source = source;
+ }
+
+ public String getTrackId() {
+ return trackId;
+ }
+
+ public void setTrackId(String trackId) {
+ this.trackId = trackId;
+ }
+ public String getTtl() {
+ return ttl;
+ }
+
+ public void setTtl(String ttl) {
+ this.ttl = ttl;
+ }
+
@Override
public String toString() {
return "ConversationMessageRequest{" +
"type=" + type +
", content=" + content +
", channelId='" + channelId + '\'' +
+ ", reportUrl='" + reportUrl + '\'' +
+ ", source=" + source +
+ ", trackID=" + trackId +
+ ", ttl=" + ttl +
'}';
}
-}
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageStatus.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageStatus.java
index 475c7b74..aee5c5f5 100644
--- a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageStatus.java
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageStatus.java
@@ -15,7 +15,25 @@ public enum ConversationMessageStatus {
READ("read"),
RECEIVED("received"),
SENT("sent"),
- UNSUPPORTED("unsupported");
+ UNSUPPORTED("unsupported"),
+ ACCEPTED("accepted"),
+ REJECTED("rejected"),
+ UNKNOWN("unknown"),
+ //WA specific statuses
+ TRANSMITTED("transmitted"),
+ //SMS specific statuses
+ DELIVERY_FAILED("delivery_failed"),
+ BUFFERED("buffered"),
+ EXPIRED("expired"),
+ //Email specific statuses
+ CLICKED("clicked"),
+ OPENED("opened"),
+ BOUNCE("bounce"),
+ SPAM_COMPLAINT("spam_complaint"),
+ OUT_OF_BOUNDED("out_of_bounded"),
+ DELAYED("delayed"),
+ LIST_UNSUBSCRIBE("list_unsubscribe"),
+ DISPATCHED("dispatched");
private final String status;
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageTag.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageTag.java
new file mode 100644
index 00000000..49314b3c
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationMessageTag.java
@@ -0,0 +1,41 @@
+package com.messagebird.objects.conversations;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * These allow tagging a message based on Facebook tags.
+ * For more information visit: https://developers.facebook.com/docs/messenger-platform/send-messages/message-tags/
+ */
+public enum ConversationMessageTag {
+ EventUpdate("event.update"),
+ PurchaseUpdate("purchase.update"),
+ AccountUpdate("account.update"),
+ HumanAgent("human_agent");
+
+ @JsonValue
+ private final String tag;
+
+ ConversationMessageTag(String tag) {
+ this.tag = tag;
+ }
+
+ @JsonCreator
+ public static ConversationMessageTag forValue(final String value) {
+ for (ConversationMessageTag tag : ConversationMessageTag.values()) {
+ if (tag.getTag().equals(value)) {
+ return tag;
+ }
+ }
+ return null;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ @Override
+ public String toString() {
+ return tag;
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationPlatformConstants.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationPlatformConstants.java
new file mode 100644
index 00000000..b9c02f49
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationPlatformConstants.java
@@ -0,0 +1,33 @@
+package com.messagebird.objects.conversations;
+
+/**
+ * Platforms are communication channels that a conversation can communicate through.
+ */
+public class ConversationPlatformConstants {
+ // PlatformSMS identifies the MessageBird SMS platform.
+ public static final String SMS = "sms";
+
+ // PlatformWhatsApp identifies the WhatsApp platform.
+ public static final String WHATSAPP = "whatsapp";
+
+ // PlatformFacebook identifies the Facebook platform.
+ public static final String FACEBOOK = "facebook";
+
+ // PlatformTelegram identifies the Telegram platform.
+ public static final String TELEGRAM = "telegram";
+
+ // PlatformLine identifies the LINE platform.
+ public static final String LINE = "line";
+
+ // PlatformWeChat identifies the WeChat platform.
+ public static final String WECHAT = "wechat";
+
+ // PlatformEmail identifies the Email platform.
+ public static final String EMAIL = "email";
+
+ // PlatformEvents identifies the Events platform
+ public static final String EVENTS = "events";
+
+ // PlatformWhatsAppSandbox identified the WhatsApp sandbox platform.
+ public static final String WHATSAPP_SANDBOX = "whatsapp_sandbox";
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationRecipientMetadata.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationRecipientMetadata.java
new file mode 100644
index 00000000..7537da85
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationRecipientMetadata.java
@@ -0,0 +1,50 @@
+package com.messagebird.objects.conversations;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * Identifies the recipient of an outbound WhatsApp message, as reported back on
+ * status webhook payloads under {@code status.metadata.recipient}. Mirrors
+ * {@link ConversationSenderMetadata} on the inbound side.
+ *
+ *
{@code userId} is the recipient's BSUID (e.g. "US.13491208655302741918");
+ * {@code parentUserId} is the parent business-scoped user ID of the enterprise
+ * that owns the business portfolio it was scoped against (e.g.
+ * "US.ENT.11815799212886844830").
+ *
+ *
Either field may be {@code null}: Meta only supplies them for accounts
+ * enrolled in the BSUID rollout, and the enclosing {@code recipient} object is
+ * omitted entirely when neither is present. This is the only place a status
+ * payload carries the recipient's own identity — {@code messageMetadata.to} is
+ * an echo of the address the message was addressed to.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ConversationRecipientMetadata {
+
+ private String userId;
+ private String parentUserId;
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getParentUserId() {
+ return parentUserId;
+ }
+
+ public void setParentUserId(String parentUserId) {
+ this.parentUserId = parentUserId;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationRecipientMetadata{" +
+ "userId='" + userId + '\'' +
+ ", parentUserId='" + parentUserId + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationSendRequest.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationSendRequest.java
new file mode 100644
index 00000000..40ace8d7
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationSendRequest.java
@@ -0,0 +1,125 @@
+package com.messagebird.objects.conversations;
+
+import java.util.Map;
+
+public class ConversationSendRequest {
+ private String to;
+ private ConversationContentType type;
+ private ConversationContent content;
+ private String from;
+ private String reportUrl;
+ private String trackId;
+ private String ttl;
+ private ConversationFallbackOption fallback;
+ private Map source;
+ private ConversationMessageTag tag;
+
+ public ConversationSendRequest(String to, ConversationContentType type, ConversationContent content, String from, String reportUrl, ConversationFallbackOption fallback, Map source, ConversationMessageTag tag) {
+ this.to = to;
+ this.type = type;
+ this.content = content;
+ this.from = from;
+ this.reportUrl = reportUrl;
+ this.fallback = fallback;
+ this.source = source;
+ this.tag = tag;
+ }
+
+ public ConversationSendRequest() {
+ }
+
+ public String getTo() {
+ return to;
+ }
+
+ public void setTo(String to) {
+ this.to = to;
+ }
+
+ public ConversationContentType getType() {
+ return type;
+ }
+
+ public void setType(ConversationContentType type) {
+ this.type = type;
+ }
+
+ public ConversationContent getContent() {
+ return content;
+ }
+
+ public void setContent(ConversationContent content) {
+ this.content = content;
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ public String getReportUrl() {
+ return reportUrl;
+ }
+
+ public void setReportUrl(String reportUrl) {
+ this.reportUrl = reportUrl;
+ }
+
+ public ConversationFallbackOption getFallback() {
+ return fallback;
+ }
+
+ public void setFallback(ConversationFallbackOption fallback) {
+ this.fallback = fallback;
+ }
+
+ public Map getSource() {
+ return source;
+ }
+
+ public void setSource(Map source) {
+ this.source = source;
+ }
+
+ public ConversationMessageTag getTag() {
+ return tag;
+ }
+
+ public void setTag(ConversationMessageTag tag) {
+ this.tag = tag;
+ }
+
+ public void setTrackId(String trackId) {
+ this.trackId = trackId;
+ }
+
+ public String getTrackId() {
+ return trackId;
+ }
+
+ public String getTtl() {
+ return ttl;
+ }
+
+ public void setTtl(String ttl) {
+ this.ttl = ttl;
+ }
+ @Override
+ public String toString() {
+ return "ConversationSendRequest{" +
+ "to='" + to + '\'' +
+ ", type=" + type +
+ ", content=" + content +
+ ", from='" + from + '\'' +
+ ", reportUrl='" + reportUrl + '\'' +
+ ", trackId='" + trackId + '\'' +
+ ", ttl='" + ttl + '\'' +
+ ", fallback=" + fallback + '\'' +
+ ", tags=" + tag +
+ ", source='" + source + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationSendResponse.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationSendResponse.java
new file mode 100644
index 00000000..8d928df6
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationSendResponse.java
@@ -0,0 +1,60 @@
+package com.messagebird.objects.conversations;
+
+public class ConversationSendResponse {
+ private String id; //messageID
+ private String status;
+ private FallbackOptionResponse fallback;
+
+ public static class FallbackOptionResponse{
+ private String id;
+
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "FallbackOptionResponse{" +
+ "id='" + id + '\'' +
+ '}';
+ }
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public FallbackOptionResponse getFallback() {
+ return fallback;
+ }
+
+ public void setFallback(FallbackOptionResponse fallback) {
+ this.fallback = fallback;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationSendResponse{" +
+ "id='" + id + '\'' +
+ ", status='" + status + '\'' +
+ ", fallback=" + fallback +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationSenderMetadata.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationSenderMetadata.java
new file mode 100644
index 00000000..b7fdcbb0
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationSenderMetadata.java
@@ -0,0 +1,66 @@
+package com.messagebird.objects.conversations;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * Metadata about the sender of a WhatsApp message. {@code userId} always
+ * contains the BSUID (e.g. "US.13491208655302741918") when Meta supplies one.
+ * When both a phone number and a BSUID are available, the phone number appears
+ * in the parent message's {@code from} field — not here.
+ *
+ *
{@code parentUserId} carries the sender's parent business-scoped user ID
+ * (e.g. "US.ENT.11815799212886844830"), which identifies the enterprise that
+ * owns the business portfolio the {@code userId} was scoped against. It is only
+ * present for accounts enrolled in Meta's parent-BSUID rollout; for everyone
+ * else it stays {@code null}.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ConversationSenderMetadata {
+
+ private String userId;
+ private String parentUserId;
+ private String username;
+ private String displayName;
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getParentUserId() {
+ return parentUserId;
+ }
+
+ public void setParentUserId(String parentUserId) {
+ this.parentUserId = parentUserId;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationSenderMetadata{" +
+ "userId='" + userId + '\'' +
+ ", parentUserId='" + parentUserId + '\'' +
+ ", username='" + username + '\'' +
+ ", displayName='" + displayName + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationStartRequest.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationStartRequest.java
index 9779bf5c..6f7c84fe 100644
--- a/api/src/main/java/com/messagebird/objects/conversations/ConversationStartRequest.java
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationStartRequest.java
@@ -1,5 +1,7 @@
package com.messagebird.objects.conversations;
+import java.util.Map;
+
/**
* Request object used for starting a conversation.
*/
@@ -8,18 +10,27 @@ public class ConversationStartRequest {
private String to;
private ConversationContentType type;
private ConversationContent content;
+ private Map source;
+ private ConversationMessageTag tag;
private String channelId;
+ private String reportUrl;
+ private String trackId;
+ private String ttl;
public ConversationStartRequest(
final String to,
final ConversationContentType type,
final ConversationContent content,
- final String channelId
+ final String channelId,
+ final Map source,
+ final ConversationMessageTag tag
) {
this.to = to;
this.type = type;
this.content = content;
this.channelId = channelId;
+ this.source = source;
+ this.tag = tag;
}
public ConversationStartRequest() {
@@ -58,13 +69,50 @@ public void setChannelId(final String channelId) {
this.channelId = channelId;
}
+ public String getReportUrl() {
+ return reportUrl;
+ }
+
+ public void setReportUrl(final String reportUrl) {
+ this.reportUrl = reportUrl;
+ }
+
+ public Map getSource() {
+ return source;
+ }
+
+ public void setSource(Map source) {
+ this.source = source;
+ }
+
+ public ConversationMessageTag getTag() {
+ return tag;
+ }
+
+ public void setTag(ConversationMessageTag tag) {
+ this.tag = tag;
+ }
+
+ public String getTrackId() {
+ return trackId;
+ }
+
+ public void setTrackId(String trackId) {
+ this.trackId = trackId;
+ }
+
@Override
public String toString() {
return "ConversationStartRequest{" +
"to='" + to + '\'' +
", type=" + type +
", content=" + content +
+ ", source=" + source +
+ ", tag=" + tag +
", channelId='" + channelId + '\'' +
+ ", reportUrl='" + reportUrl + '\'' +
+ ", trackId='" + trackId + '\'' +
+ ", ttl='" + ttl + '\'' +
'}';
}
-}
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMessageMetadata.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMessageMetadata.java
new file mode 100644
index 00000000..47a4a8d1
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMessageMetadata.java
@@ -0,0 +1,92 @@
+package com.messagebird.objects.conversations;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * The {@code messageMetadata} block delivered inside status webhook payloads
+ * (e.g. {@code statusSent}, {@code statusDelivered}). Reflects the original
+ * message that triggered the status update.
+ *
+ *
This class is not produced by any SDK request — it is a standalone POJO
+ * intended for consumers who deserialize incoming webhook payloads in their
+ * own HTTP handlers. Use it via {@code ObjectMapper.readValue(body, ...)}.
+ *
+ *
Both {@code from} and {@code to} accept either a phone number or a
+ * WhatsApp Business-Scoped User ID (BSUID, e.g. "US.13491208655302741918").
+ * The BSUID is also available via {@code metadata.sender.userId}.
+ *
+ *
{@code to} echoes back the address the message was originally addressed
+ * to, so it is not a reliable source of the recipient's BSUID. That identity
+ * lives alongside this block, under {@code status.metadata.recipient} — see
+ * {@link ConversationStatusMetadata}.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ConversationStatusMessageMetadata {
+
+ private String id;
+ private String from;
+ private String to;
+ private String type;
+ private ConversationContent content;
+ private ConversationMessageMetadata metadata;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ public String getTo() {
+ return to;
+ }
+
+ public void setTo(String to) {
+ this.to = to;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public ConversationContent getContent() {
+ return content;
+ }
+
+ public void setContent(ConversationContent content) {
+ this.content = content;
+ }
+
+ public ConversationMessageMetadata getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(ConversationMessageMetadata metadata) {
+ this.metadata = metadata;
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationStatusMessageMetadata{" +
+ "id='" + id + '\'' +
+ ", from='" + from + '\'' +
+ ", to='" + to + '\'' +
+ ", type='" + type + '\'' +
+ ", content=" + content +
+ ", metadata=" + metadata +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMetadata.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMetadata.java
new file mode 100644
index 00000000..0b4d5f7f
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationStatusMetadata.java
@@ -0,0 +1,85 @@
+package com.messagebird.objects.conversations;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * The {@code status.metadata} block delivered inside status webhook payloads
+ * (e.g. {@code statusSent}, {@code statusDelivered}).
+ *
+ *
This class is not produced by any SDK request — it is a standalone POJO
+ * intended for consumers who deserialize incoming webhook payloads in their own
+ * HTTP handlers. Use it via {@code ObjectMapper.readValue(body, ...)}.
+ *
+ *
Note the mixed casing of this object. {@code pricing} and
+ * {@code conversation} are near-verbatim passthroughs of Meta's own objects and
+ * so keep their snake_case keys ({@code pricing_model}, {@code category}, …);
+ * they are exposed here as raw maps rather than modelled types, because their
+ * contents track Meta's schema rather than ours. {@code recipient} is ours and
+ * follows the camelCase convention used everywhere else in the API. Any other
+ * key present on the payload — for example {@code biz_opaque_callback_data} —
+ * is collected into {@link #getAdditionalProperties()} rather than dropped.
+ *
+ *
{@code recipient} is absent from payloads for accounts that never receive
+ * BSUIDs, in which case {@link #getRecipient()} returns {@code null}.
+ */
+public class ConversationStatusMetadata {
+
+ private Map pricing;
+ private Map conversation;
+ private ConversationRecipientMetadata recipient;
+ private final Map additionalProperties = new LinkedHashMap<>();
+
+ public Map getPricing() {
+ return pricing;
+ }
+
+ public void setPricing(Map pricing) {
+ this.pricing = pricing;
+ }
+
+ public Map getConversation() {
+ return conversation;
+ }
+
+ public void setConversation(Map conversation) {
+ this.conversation = conversation;
+ }
+
+ public ConversationRecipientMetadata getRecipient() {
+ return recipient;
+ }
+
+ public void setRecipient(ConversationRecipientMetadata recipient) {
+ this.recipient = recipient;
+ }
+
+ /**
+ * Every key on the payload that has no dedicated accessor above, in the
+ * order it was encountered. Empty when the payload holds nothing else.
+ *
+ * @return the unmodelled remainder of the metadata object
+ */
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return "ConversationStatusMetadata{" +
+ "pricing=" + pricing +
+ ", conversation=" + conversation +
+ ", recipient=" + recipient +
+ ", additionalProperties=" + additionalProperties +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/ConversationUpdateRequest.java b/api/src/main/java/com/messagebird/objects/conversations/ConversationUpdateRequest.java
new file mode 100644
index 00000000..9a67a516
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/ConversationUpdateRequest.java
@@ -0,0 +1,29 @@
+package com.messagebird.objects.conversations;
+
+import java.util.Objects;
+
+public class ConversationUpdateRequest {
+
+ private final ConversationStatus status;
+
+ public ConversationUpdateRequest(ConversationStatus status) {
+ this.status = status;
+ }
+
+ public ConversationStatus getStatus() {
+ return status;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ConversationUpdateRequest that = (ConversationUpdateRequest) o;
+ return status == that.status;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status);
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/HSMCurrency.java b/api/src/main/java/com/messagebird/objects/conversations/HSMCurrency.java
new file mode 100644
index 00000000..24ea4be6
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/HSMCurrency.java
@@ -0,0 +1,31 @@
+package com.messagebird.objects.conversations;
+
+public class HSMCurrency {
+
+ private String currencyCode;
+ private int amount;
+
+ public String getCurrencyCode() {
+ return currencyCode;
+ }
+
+ public void setCurrencyCode(String currencyCode) {
+ this.currencyCode = currencyCode;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public void setAmount(int amount) {
+ this.amount = amount;
+ }
+
+ @Override
+ public String toString() {
+ return "HSMCurrency{" +
+ "currencyCode='" + currencyCode + '\'' +
+ ", amount=" + amount +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/Media.java b/api/src/main/java/com/messagebird/objects/conversations/Media.java
new file mode 100644
index 00000000..4a9bf4ea
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/Media.java
@@ -0,0 +1,31 @@
+package com.messagebird.objects.conversations;
+
+public class Media {
+
+ private String url;
+ private String caption;
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getCaption() {
+ return caption;
+ }
+
+ public void setCaption(String caption) {
+ this.caption = caption;
+ }
+
+ @Override
+ public String toString() {
+ return "Media{" +
+ "url='" + url + '\'' +
+ ", caption='" + caption + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/MessageComponent.java b/api/src/main/java/com/messagebird/objects/conversations/MessageComponent.java
new file mode 100644
index 00000000..d3a7c20b
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/MessageComponent.java
@@ -0,0 +1,82 @@
+package com.messagebird.objects.conversations;
+
+import java.util.List;
+
+public class MessageComponent {
+
+ private MessageComponentType type;
+ private String sub_type;
+ private int index;
+ private List parameters;
+ private int card_index;
+ private List cards;
+ private List components;
+
+ public void setType(MessageComponentType type) {
+ this.type = type;
+ }
+
+ public MessageComponentType getType() {
+ return type;
+ }
+
+ public String getSub_type() {
+ return sub_type;
+ }
+
+ public void setSub_type(String sub_type) {
+ this.sub_type = sub_type;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+ public List getParameters() {
+ return parameters;
+ }
+
+ public void setParameters(List parameters) {
+ this.parameters = parameters;
+ }
+
+ public void setCards(List cards) {
+ this.cards = cards;
+ }
+
+ public List getCards() {
+ return cards;
+ }
+
+ public int getCard_index() {
+ return card_index;
+ }
+
+ public void setCard_index(int card_index) {
+ this.card_index = card_index;
+ }
+
+ public List getComponents() {
+ return components;
+ }
+
+ public void setComponents(List components) {
+ this.components = components;
+ }
+
+ @Override
+ public String toString() {
+ return "MessageComponent{" +
+ "type='" + type + '\'' +
+ ", sub_type='" + sub_type + '\'' +
+ ", index=" + index + '\'' +
+ ", parameters=" + parameters + '\'' +
+ ", components=" + components + '\'' +
+ ", cards=" + cards +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/MessageComponentType.java b/api/src/main/java/com/messagebird/objects/conversations/MessageComponentType.java
new file mode 100644
index 00000000..9f9290cf
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/MessageComponentType.java
@@ -0,0 +1,54 @@
+package com.messagebird.objects.conversations;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import java.util.*;
+
+public enum MessageComponentType {
+
+ HEADER("header"),
+ BODY("body"),
+ FOOTER("footer"),
+ BUTTON("button"),
+ CARD("card"),
+ CAROUSEL("carousel"),
+ LIMITED_TIME_OFFER("limited_time_offer"),
+ COPY_CODE("copy_code");
+
+ private static final Map TYPE_MAP;
+
+ static {
+ Map map = new HashMap<>();
+ for (MessageComponentType componentType : MessageComponentType.values()) {
+ map.put(componentType.getType().toLowerCase(), componentType);
+ }
+ TYPE_MAP = Collections.unmodifiableMap(map);
+ }
+
+ private final String type;
+
+ MessageComponentType(final String type) {
+ this.type = type;
+ }
+
+ @JsonCreator
+ public static MessageComponentType forValue(String value) {
+ Objects.requireNonNull(value, "Value cannot be null");
+ return TYPE_MAP.get(value.toLowerCase(Locale.ROOT));
+ }
+
+ @JsonValue
+ public String toJson() {
+ return getType();
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return getType();
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/MessageParam.java b/api/src/main/java/com/messagebird/objects/conversations/MessageParam.java
new file mode 100644
index 00000000..6432369f
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/MessageParam.java
@@ -0,0 +1,125 @@
+package com.messagebird.objects.conversations;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.StringUtils;
+
+public class MessageParam {
+
+ private TemplateMediaType type;
+ private String text;
+ private String payload;
+ private HSMCurrency currency;
+ private String dateTime;
+ private Media document;
+ private Media image;
+ private Media video;
+ @JsonProperty("expiration_time")
+ private String expirationTime;
+ @JsonProperty("coupon_code")
+ private String couponCode;
+
+ public TemplateMediaType getType() {
+ return type;
+ }
+
+ public void setType(TemplateMediaType type) {
+ this.type = type;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ if (StringUtils.isBlank(text)) {
+ throw new IllegalArgumentException("Text cannot be null or empty");
+ }
+ this.text = text;
+ }
+
+ public String getPayload() {
+ return payload;
+ }
+
+ public void setPayload(String payload) {
+ this.payload = payload;
+ }
+
+ public HSMCurrency getCurrency() {
+ return currency;
+ }
+
+ public void setCurrency(HSMCurrency currency) {
+ this.currency = currency;
+ }
+
+ public String getDateTime() {
+ return dateTime;
+ }
+
+ public void setDateTime(String dateTime) {
+ if (StringUtils.isBlank(dateTime)) {
+ throw new IllegalArgumentException("dateTime cannot be null or empty");
+ }
+ this.dateTime = dateTime;
+ }
+
+ public Media getDocument() {
+ return document;
+ }
+
+ public void setDocument(Media document) {
+ this.document = document;
+ }
+
+ public Media getImage() {
+ return image;
+ }
+
+ public void setImage(Media image) {
+ this.image = image;
+ }
+
+ public Media getVideo() { return video; }
+
+ public void setVideo(Media video) { this.video = video; }
+
+ public String getExpirationTime() {
+ return expirationTime;
+ }
+
+ public void setExpirationTime(String expirationTime) {
+ if (StringUtils.isBlank(expirationTime)) {
+ throw new IllegalArgumentException("expirationTime cannot be null or empty");
+ }
+ this.expirationTime = expirationTime;
+ }
+
+ public String getCouponCode() {
+ return couponCode;
+ }
+
+ public void setCouponCode(String couponCode) {
+ if (StringUtils.isBlank(couponCode)) {
+ throw new IllegalArgumentException("couponCode cannot be null or empty");
+ }
+ this.couponCode = couponCode;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("MessageParam{");
+ sb.append("type=").append(type)
+ .append(", text='").append(text).append('\'')
+ .append(", payload='").append(payload).append('\'')
+ .append(", currency=").append(currency)
+ .append(", dateTime='").append(dateTime).append('\'')
+ .append(", document=").append(document)
+ .append(", image=").append(image)
+ .append(", video=").append(video)
+ .append(", expirationTime='").append(expirationTime).append('\'')
+ .append(", couponCode='").append(couponCode).append('\'')
+ .append('}');
+ return sb.toString();
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/conversations/TemplateMediaType.java b/api/src/main/java/com/messagebird/objects/conversations/TemplateMediaType.java
new file mode 100644
index 00000000..580dcc97
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/conversations/TemplateMediaType.java
@@ -0,0 +1,60 @@
+package com.messagebird.objects.conversations;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Collections;
+
+public enum TemplateMediaType {
+
+ IMAGE("image"),
+ DOCUMENT("document"),
+ VIDEO("video"),
+ TEXT("text"),
+ CURRENCY("currency"),
+ DATETIME("date_time"),
+ PAYLOAD("payload"),
+ EXPIRATION_TIME("expiration_time"),
+ COUPON_CODE("coupon_code");
+
+ private static final Map TYPE_MAP;
+
+ static {
+ Map map = new HashMap<>();
+ for (TemplateMediaType templateMediaType : TemplateMediaType.values()) {
+ map.put(templateMediaType.getType().toLowerCase(), templateMediaType);
+ }
+ TYPE_MAP = Collections.unmodifiableMap(map);
+ }
+
+
+ private final String type;
+
+ TemplateMediaType(final String type) {
+ this.type = type;
+ }
+
+ @JsonCreator
+ public static TemplateMediaType forValue(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Value cannot be null");
+ }
+ return TYPE_MAP.get(value.toLowerCase());
+ }
+
+ @JsonValue
+ public String toJson() {
+ return getType();
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return getType();
+ }
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMCategory.java b/api/src/main/java/com/messagebird/objects/integrations/HSMCategory.java
new file mode 100644
index 00000000..3edb1db9
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMCategory.java
@@ -0,0 +1,48 @@
+package com.messagebird.objects.integrations;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * An enum for HSMComponentFormat
+ *
+ * @see HSMComponentFormat
+ * @author ssk910
+ */
+public enum HSMCategory {
+
+ AUTHENTICATION("AUTHENTICATION"),
+ UTILITY("UTILITY"),
+ MARKETING("MARKETING");
+
+ private final String category;
+
+ HSMCategory(String category) {
+ this.category = category;
+ }
+
+ @JsonCreator
+ public static HSMCategory forValue(String value) {
+ for (HSMCategory hsmCategory : HSMCategory.values()) {
+ if (hsmCategory.getCategory().equals(value)) {
+ return hsmCategory;
+ }
+ }
+
+ return null;
+ }
+
+ @JsonValue
+ public String toJson() {
+ return getCategory();
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ @Override
+ public String toString() {
+ return getCategory();
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponent.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponent.java
new file mode 100644
index 00000000..ed93847f
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponent.java
@@ -0,0 +1,196 @@
+package com.messagebird.objects.integrations;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.List;
+
+/**
+ * A class for HSMComponent object
+ *
+ * @see HSMComponent
+ * @author ssk910
+ */
+public class HSMComponent {
+
+ private HSMComponentType type;
+ private HSMComponentFormat format;
+ private String text;
+ @JsonProperty("add_security_recommendation")
+ private Boolean addSecurityRecommendation;
+ @JsonProperty("code_expiration_minutes")
+ private Integer codeExpirationMinutes;
+ private List buttons;
+ @JsonProperty("has_expiration")
+ private Boolean hasExpiration;
+
+ private List cards;
+
+ private HSMExample example;
+
+ public HSMComponentType getType() {
+ return type;
+ }
+
+ public void setType(HSMComponentType type) {
+ this.type = type;
+ }
+
+ public HSMComponentFormat getFormat() {
+ return format;
+ }
+
+ public void setFormat(HSMComponentFormat format) {
+ this.format = format;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ if (StringUtils.isBlank(text)) {
+ throw new IllegalArgumentException("Text cannot be null or empty");
+ }
+ this.text = text;
+ }
+
+ public List getButtons() {
+ return buttons;
+ }
+
+ public void setButtons(List buttons) {
+ this.buttons = buttons;
+ }
+
+ public List getCards() {
+ return cards;
+ }
+
+ public void setCards(List cards) {
+ this.cards = cards;
+ }
+
+ public HSMExample getExample() {
+ return example;
+ }
+
+ public void setExample(HSMExample example) {
+ this.example = example;
+ }
+
+ public Boolean getAddSecurityRecommendation() {
+ return addSecurityRecommendation;
+ }
+
+ public void setAddSecurityRecommendation(Boolean addSecurityRecommendation) {
+ this.addSecurityRecommendation = addSecurityRecommendation;
+ }
+
+ public Integer getCodeExpirationMinutes() {
+ return codeExpirationMinutes;
+ }
+
+ public void setCodeExpirationMinutes(Integer codeExpirationMinutes) {
+ this.codeExpirationMinutes = codeExpirationMinutes;
+ }
+
+ public Boolean getHasExpiration() {
+ return hasExpiration;
+ }
+
+ public void setHasExpiration(Boolean hasExpiration) {
+ this.hasExpiration = hasExpiration;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("HSMComponent{");
+ sb.append("type=").append(type)
+ .append(", format=").append(format)
+ .append(", text='").append(text).append('\'')
+ .append(", addSecurityRecommendation=").append(addSecurityRecommendation)
+ .append(", codeExpirationMinutes=").append(codeExpirationMinutes)
+ .append(", buttons=").append(buttons)
+ .append(", hasExpiration=").append(hasExpiration)
+ .append(", cards=").append(cards)
+ .append(", example=").append(example)
+ .append('}');
+ return sb.toString();
+ }
+
+ /**
+ * Check if this component is valid.
+ *
+ * @throws IllegalArgumentException Occurs when validation is not passed.
+ */
+ public void validateComponent() throws IllegalArgumentException {
+ try {
+ this.validateButtons();
+ this.validateComponentExample();
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Component validation failed: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Check if button list is valid.
+ *
+ * @throws IllegalArgumentException Occurs when validation is not passed.
+ */
+ private void validateButtons() throws IllegalArgumentException {
+ if (this.buttons == null) {
+ return;
+ }
+
+ for (final HSMComponentButton button : this.buttons) {
+ button.validateButtonExample();
+ }
+ }
+
+ /**
+ * Check for header_text and header_url.
+ *
+ * @throws IllegalArgumentException Occurs when {@code header_text} or {@code header_url} is not able to use.
+ */
+ private void validateComponentExample() throws IllegalArgumentException {
+ final boolean isExampleNotNull = this.example != null;
+ final boolean isHeaderTextNotEmpty =
+ isExampleNotNull && !(this.example.getHeader_text() == null || this.example.getHeader_text()
+ .isEmpty());
+ final boolean isHeaderUrlNotEmpty =
+ isExampleNotNull && !(this.example.getHeader_url() == null || this.example.getHeader_url()
+ .isEmpty());
+
+ if (isHeaderTextNotEmpty) {
+ this.checkHeaderText();
+ }
+
+ if (isHeaderUrlNotEmpty) {
+ this.checkHeaderUrl();
+ }
+ }
+
+ /**
+ * Check if header_text is able to use.
+ *
+ * @throws IllegalArgumentException Occurs when type is not {@code HEADER} and format is not {@code TEXT}.
+ */
+ private void checkHeaderText() throws IllegalArgumentException {
+ if (!(HSMComponentType.HEADER.equals(type) && HSMComponentFormat.TEXT.equals(format))) {
+ throw new IllegalArgumentException("\"header_text\" is available for only HEADER type and TEXT format.");
+ }
+ }
+
+ /**
+ * Check if header_url is able to use.
+ *
+ * @throws IllegalArgumentException Occurs when type is not {@code HEADER} and format is not {@code IMAGE}.
+ */
+ private void checkHeaderUrl() throws IllegalArgumentException {
+ if (!(HSMComponentType.HEADER.equals(type) &&
+ (HSMComponentFormat.IMAGE.equals(format) || HSMComponentFormat.VIDEO.equals(format) || HSMComponentFormat.DOCUMENT.equals(format)))) {
+ throw new IllegalArgumentException("\"header_url\" is available for only HEADER type and IMAGE, VIDEO, or DOCUMENT formats.");
+ }
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButton.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButton.java
new file mode 100644
index 00000000..75204df0
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButton.java
@@ -0,0 +1,132 @@
+package com.messagebird.objects.integrations;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * HSMComponentButton
+ *
+ * @see HSMComponentButton
+ * @author ssk910
+ */
+public class HSMComponentButton {
+
+ private HSMComponentButtonType type;
+ private String text;
+ private String url;
+ private String phone_number;
+ private List example;
+
+ //fields used by the authentification template
+ @JsonProperty("otp_type")
+ private HSMOTPButtonType otpType;
+ @JsonProperty("autofill_text")
+ private String autofillText;
+ @JsonProperty("package_name")
+ private String packageName;
+ @JsonProperty("signature_hash")
+ private String signatureHash;
+
+ public HSMComponentButtonType getType() {
+ return type;
+ }
+
+ public void setType(HSMComponentButtonType type) {
+ this.type = type;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getPhone_number() {
+ return phone_number;
+ }
+
+ public void setPhone_number(String phone_number) {
+ this.phone_number = phone_number;
+ }
+
+ public List getExample() {
+ return example;
+ }
+
+ public void setExample(List example) {
+ this.example = example;
+ }
+ public HSMOTPButtonType getOtpType() {
+ return otpType;
+ }
+
+ public void setOtpType(HSMOTPButtonType otpType) {
+ this.otpType = otpType;
+ }
+
+ public String getAutofillText() {
+ return autofillText;
+ }
+
+ public void setAutofillText(String autofillText) {
+ this.autofillText = autofillText;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
+ }
+
+ public String getSignatureHash() {
+ return signatureHash;
+ }
+
+ public void setSignatureHash(String signatureHash) {
+ this.signatureHash = signatureHash;
+ }
+ @Override
+ public String toString() {
+ return "HSMComponentButton{" +
+ "type=" + type +
+ ", text='" + text + '\'' +
+ ", url='" + url + '\'' +
+ ", phone_number='" + phone_number + '\'' +
+ ", example=" + example +
+ '}';
+ }
+
+ /**
+ * Check if example field is able to use.
+ *
+ * @throws IllegalArgumentException Occurs if button type is not {@code URL} or {@code QUICK_REPLY}.
+ */
+ public void validateButtonExample() throws IllegalArgumentException {
+ final boolean isExampleEmpty = this.example == null || this.example.isEmpty();
+ final boolean isNotProperType = !(this.type.equals(HSMComponentButtonType.URL)
+ || this.type.equals(HSMComponentButtonType.QUICK_REPLY)
+ || this.type.equals(HSMComponentButtonType.COPY_CODE)
+ );
+
+ if (isExampleEmpty) {
+ return;
+ }
+
+ if (isNotProperType) {
+ throw new IllegalArgumentException("An example field in HSMComponentButton is available for only URL or QUICK_REPLY button types.");
+ }
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButtonType.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButtonType.java
new file mode 100644
index 00000000..06e6eb91
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentButtonType.java
@@ -0,0 +1,50 @@
+package com.messagebird.objects.integrations;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * HSMComponentButtonType
+ *
+ * @see HSMComponentButtonType
+ * @author ssk910
+ */
+public enum HSMComponentButtonType {
+
+ PHONE_NUMBER("PHONE_NUMBER"),
+ URL("URL"),
+ QUICK_REPLY("QUICK_REPLY"),
+ OTP("OTP"),
+ COPY_CODE("COPY_CODE");
+
+ private final String type;
+
+ HSMComponentButtonType(String type) {
+ this.type = type;
+ }
+
+ @JsonCreator
+ public static HSMComponentButtonType forValue(String value) {
+ for (HSMComponentButtonType hsmComponentButtonType : HSMComponentButtonType.values()) {
+ if (hsmComponentButtonType.getType().equals(value)) {
+ return hsmComponentButtonType;
+ }
+ }
+
+ return null;
+ }
+
+ @JsonValue
+ public String toJson() {
+ return getType();
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return getType();
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentCard.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentCard.java
new file mode 100644
index 00000000..23411941
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentCard.java
@@ -0,0 +1,21 @@
+package com.messagebird.objects.integrations;
+
+import java.util.List;
+
+/**
+ * HSMComponentCard
+ *
+ * @author AlexL-mb
+ * @see HSMComponentCard
+ */
+public class HSMComponentCard {
+ private List components;
+
+ public List getComponents() {
+ return components;
+ }
+
+ public void setComponents(List components) {
+ this.components = components;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentFormat.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentFormat.java
new file mode 100644
index 00000000..ac7d199d
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentFormat.java
@@ -0,0 +1,49 @@
+package com.messagebird.objects.integrations;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * An enum for HSMComponentFormat
+ *
+ * @see HSMComponentFormat
+ * @author ssk910
+ */
+public enum HSMComponentFormat {
+
+ TEXT("TEXT"),
+ IMAGE("IMAGE"),
+ DOCUMENT("DOCUMENT"),
+ VIDEO("VIDEO");
+
+ private final String format;
+
+ HSMComponentFormat(String format) {
+ this.format = format;
+ }
+
+ @JsonCreator
+ public static HSMComponentFormat forValue(String value) {
+ for (HSMComponentFormat hsmComponentFormat : HSMComponentFormat.values()) {
+ if (hsmComponentFormat.getFormat().equals(value)) {
+ return hsmComponentFormat;
+ }
+ }
+
+ return null;
+ }
+
+ @JsonValue
+ public String toJson() {
+ return getFormat();
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ @Override
+ public String toString() {
+ return getFormat();
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMComponentType.java b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentType.java
new file mode 100644
index 00000000..8610146a
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMComponentType.java
@@ -0,0 +1,59 @@
+package com.messagebird.objects.integrations;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * An enum for HSMComponentType
+ *
+ * @see HSMComponentType
+ */
+public enum HSMComponentType {
+ BODY("BODY"),
+ HEADER("HEADER"),
+ FOOTER("FOOTER"),
+ BUTTONS("BUTTONS"),
+ CAROUSEL("CAROUSEL"),
+ LIMITED_TIME_OFFER("LIMITED_TIME_OFFER");
+
+ private static final Map TYPE_MAP;
+
+ static {
+ Map map = new HashMap<>();
+ for (HSMComponentType hsmComponentType : HSMComponentType.values()) {
+ map.put(hsmComponentType.getType().toLowerCase(), hsmComponentType);
+ }
+ TYPE_MAP = Collections.unmodifiableMap(map);
+ }
+
+ private final String type;
+
+ HSMComponentType(String type) {
+ this.type = type;
+ }
+
+ @JsonCreator
+ public static HSMComponentType forValue(String value) {
+ Objects.requireNonNull(value, "Value cannot be null");
+ return TYPE_MAP.get(value.toLowerCase(Locale.ROOT));
+ }
+
+ @JsonValue
+ public String toJson() {
+ return getType();
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return getType();
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMExample.java b/api/src/main/java/com/messagebird/objects/integrations/HSMExample.java
new file mode 100644
index 00000000..a28fcc38
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMExample.java
@@ -0,0 +1,54 @@
+package com.messagebird.objects.integrations;
+
+import java.util.List;
+
+/**
+ * HSMExample object
+ *
+ * @see HSMExample object
+ * @author ssk910
+ */
+public class HSMExample {
+
+ /* Example values for HEADER type components, TEXT format */
+ private List header_text;
+
+ /* Example set of values for the body text variables */
+ private List> body_text;
+
+ /* Example values for HEADER type components, IMAGE format */
+ private List header_url;
+
+ public List getHeader_text() {
+ return header_text;
+ }
+
+ public void setHeader_text(List header_text) {
+ this.header_text = header_text;
+ }
+
+ public List> getBody_text() {
+ return body_text;
+ }
+
+ public void setBody_text(List> body_text) {
+ this.body_text = body_text;
+ }
+
+ public List getHeader_url() {
+ return header_url;
+ }
+
+ public void setHeader_url(List header_url) {
+ this.header_url = header_url;
+ }
+
+ @Override
+ public String toString() {
+ return "HSMExample{" +
+ "header_text=" + header_text +
+ ", body_text=" + body_text +
+ ", header_url=" + header_url +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMOTPButtonType.java b/api/src/main/java/com/messagebird/objects/integrations/HSMOTPButtonType.java
new file mode 100644
index 00000000..499ef1f3
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMOTPButtonType.java
@@ -0,0 +1,38 @@
+package com.messagebird.objects.integrations;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+public enum HSMOTPButtonType {
+ ONE_TAP("ONE_TAP"),
+ COPY_CODE("COPY_CODE");
+
+ private final String type;
+
+ HSMOTPButtonType(String type) {
+ this.type = type;
+ }
+ @JsonCreator
+ public static HSMOTPButtonType forValue(String value) {
+ for (HSMOTPButtonType OTPButtonType : HSMOTPButtonType.values()) {
+ if (OTPButtonType.getType().equals(value)) {
+ return OTPButtonType;
+ }
+ }
+ return null;
+ }
+
+ @JsonValue
+ public String toJson() {
+ return getType();
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return getType();
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMQualityScore.java b/api/src/main/java/com/messagebird/objects/integrations/HSMQualityScore.java
new file mode 100644
index 00000000..112492fd
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMQualityScore.java
@@ -0,0 +1,42 @@
+package com.messagebird.objects.integrations;
+
+import java.util.List;
+
+public class HSMQualityScore {
+ private String score;
+ private long date;
+ private List reasons;
+
+ public String getScore() {
+ return score;
+ }
+
+ public void setScore(String score) {
+ this.score = score;
+ }
+
+ public long getDate() {
+ return date;
+ }
+
+ public void setDate(long date) {
+ this.date = date;
+ }
+
+ public List getReasons() {
+ return reasons;
+ }
+
+ public void setReasons(List reasons) {
+ this.reasons = reasons;
+ }
+
+ @Override
+ public String toString() {
+ return "HSMQualityScore{" +
+ "score='" + score + '\'' +
+ ", date=" + date +
+ ", reasons=" + reasons +
+ '}';
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/HSMStatus.java b/api/src/main/java/com/messagebird/objects/integrations/HSMStatus.java
new file mode 100644
index 00000000..1b1980f1
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/HSMStatus.java
@@ -0,0 +1,53 @@
+package com.messagebird.objects.integrations;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * An enum for HSMStatus object
+ *
+ * @see HSMStatus object
+ * @author ssk910
+ */
+public enum HSMStatus {
+
+ NEW("NEW"),
+ APPROVED("APPROVED"),
+ PENDING("PENDING"),
+ REJECTED("REJECTED"),
+ PENDING_DELETION("PENDING_DELETION"),
+ DELETED("DELETED"),
+ DISABLED("DISABLED"),
+ PAUSED("PAUSED");
+
+ private final String status;
+
+ HSMStatus(String status) {
+ this.status = status;
+ }
+
+ @JsonCreator
+ public static HSMStatus forValue(String value) {
+ for (HSMStatus hsmStatus : HSMStatus.values()) {
+ if (hsmStatus.getStatus().equals(value)) {
+ return hsmStatus;
+ }
+ }
+
+ return null;
+ }
+
+ @JsonValue
+ public String toJson() {
+ return getStatus();
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ @Override
+ public String toString() {
+ return getStatus();
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/Template.java b/api/src/main/java/com/messagebird/objects/integrations/Template.java
new file mode 100644
index 00000000..f6a83e93
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/Template.java
@@ -0,0 +1,173 @@
+package com.messagebird.objects.integrations;
+
+import java.util.List;
+
+/**
+ * Template Object as integrations API request.
+ *
+ * @see Integrations API
+ * @author ssk910
+ */
+public class Template {
+
+ private String name;
+ private String language;
+ private String wabaID;
+ private List components;
+ private HSMCategory category;
+ private boolean ctaURLLinkTrackingOptedOut;
+
+ public Template() {
+ }
+
+
+ public Template(String name, String language, String wabaID,
+ List components, HSMCategory category, boolean ctaURLLinkTrackingOptedOut) {
+ this.name = name;
+ this.language = language;
+ this.wabaID = wabaID;
+ this.components = components;
+ this.category = category;
+ this.ctaURLLinkTrackingOptedOut = ctaURLLinkTrackingOptedOut;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
+ public String getWABAID() {
+ return wabaID;
+ }
+
+ public void setWABAID(String wabaID) {
+ this.wabaID = wabaID;
+ }
+
+ public List getComponents() {
+ return components;
+ }
+
+ public void setComponents(List components) {
+ this.components = components;
+ }
+
+ public HSMCategory getCategory() {
+ return category;
+ }
+
+ public void setCategory(HSMCategory category) {
+ this.category = category;
+ }
+
+ public void setCtaURLLinkTrackingOptedOut (boolean ctaURLLinkTrackingOptedOut) {
+ this.ctaURLLinkTrackingOptedOut = ctaURLLinkTrackingOptedOut;
+ }
+
+ public boolean getCtaURLLinkTrackingOptedOut () {
+ return ctaURLLinkTrackingOptedOut;
+ }
+
+ @Override
+ public String toString() {
+ return "WhatsAppTemplate{" +
+ "name='" + name + '\'' +
+ ", language='" + language + '\'' +
+ ", wabaID='" + wabaID + '\'' +
+ ", components=" + components +
+ ", category='" + category + '\'' +
+ ", ctaURLLinkTrackingOptedOut='" + ctaURLLinkTrackingOptedOut + '\'' +
+ '}';
+ }
+
+ /**
+ * Validate required fields: components, name, language, category
+ *
+ * @throws IllegalArgumentException if required fields are invalid.
+ */
+ public void validate() throws IllegalArgumentException {
+ this.validateComponents();
+ this.validateName();
+ this.validateLanguage();
+ this.validateWABAID();
+ this.validateCategory();
+ }
+
+ /**
+ * Check if components field is valid.
+ *
+ * @throws IllegalArgumentException If components field is null or empty list.
+ */
+ private void validateComponents() throws IllegalArgumentException {
+ final boolean componentsNotEmpty = !(this.components == null || this.components.isEmpty());
+
+ if (componentsNotEmpty) {
+ for (final HSMComponent component : this.components) {
+ component.validateComponent();
+ }
+ } else {
+ throw new IllegalArgumentException("A \"components\" field is required and should not be empty list.");
+ }
+ }
+
+ /**
+ * Check if name field is valid.
+ *
+ * @throws IllegalArgumentException If name field is null or empty string.
+ */
+ private void validateName() {
+ if (this.name == null) {
+ throw new IllegalArgumentException("A \"name\" field is required.");
+ } else if (this.name.length() == 0) {
+ throw new IllegalArgumentException("A \"name\" field can not be an empty string.");
+ }
+ }
+
+ /**
+ * Check if language field is valid.
+ *
+ * @throws IllegalArgumentException If language field is null or empty string.
+ */
+ private void validateLanguage() {
+ if (this.language == null) {
+ throw new IllegalArgumentException("A \"language\" field is required.");
+ } else if (this.language.length() == 0) {
+ throw new IllegalArgumentException("A \"language\" field can not be an empty string.");
+ }
+ }
+
+ /**
+ * Check if wabaID field is valid.
+ *
+ * @throws IllegalArgumentException If wabaID field is null or empty string.
+ */
+ private void validateWABAID() {
+ if (this.wabaID == null) {
+ throw new IllegalArgumentException("A \"wabaID\" field is required.");
+ } else if (this.wabaID.length() == 0) {
+ throw new IllegalArgumentException("A \"wabaID\" field can not be an empty string.");
+ }
+ }
+
+ /**
+ * Check if category field is valid.
+ *
+ * @throws IllegalArgumentException If category field is null.
+ */
+ private void validateCategory() {
+ if (this.category == null) {
+ throw new IllegalArgumentException("A \"category\" field is required.");
+ }
+ }
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/TemplateList.java b/api/src/main/java/com/messagebird/objects/integrations/TemplateList.java
new file mode 100644
index 00000000..5cea4023
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/TemplateList.java
@@ -0,0 +1,12 @@
+package com.messagebird.objects.integrations;
+
+import com.messagebird.objects.ListBase;
+
+/**
+ * Response object representing the Template list type.
+ *
+ * @author ssk910
+ */
+public class TemplateList extends ListBase {
+
+}
diff --git a/api/src/main/java/com/messagebird/objects/integrations/TemplateResponse.java b/api/src/main/java/com/messagebird/objects/integrations/TemplateResponse.java
new file mode 100644
index 00000000..3c75fabe
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/integrations/TemplateResponse.java
@@ -0,0 +1,149 @@
+package com.messagebird.objects.integrations;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Template response using integrations API.
+ *
+ * @author ssk910
+ */
+public class TemplateResponse implements Serializable {
+
+ private static final long serialVersionUID = 7154209824478715861L;
+ private String name;
+ private String language;
+ private HSMCategory category;
+ private List components;
+ private HSMStatus status;
+ private String rejectedReason;
+ private String wabaID;
+ private String namespace;
+
+ private boolean ctaURLLinkTrackingOptedOut;
+
+ private HSMQualityScore qualityScore;
+
+ private Date createdAt;
+ private Date updatedAt;
+
+ public static long getSerialVersionUID() {
+ return serialVersionUID;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
+ public HSMCategory getCategory() {
+ return category;
+ }
+
+ public void setCategory(HSMCategory category) {
+ this.category = category;
+ }
+
+ public List getComponents() {
+ return components;
+ }
+
+ public void setComponents(List components) {
+ this.components = components;
+ }
+
+ public HSMStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(HSMStatus status) {
+ this.status = status;
+ }
+
+ public String getRejectedReason() {
+ return rejectedReason;
+ }
+
+ public void setRejectedReason(String rejectedReason) {
+ this.rejectedReason = rejectedReason;
+ }
+
+ public String getWabaID() {
+ return wabaID;
+ }
+
+ public void setWabaID(String wabaID) {
+ this.wabaID = wabaID;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ public boolean isCtaURLLinkTrackingOptedOut() {
+ return ctaURLLinkTrackingOptedOut;
+ }
+
+ public void setCtaURLLinkTrackingOptedOut(boolean ctaURLLinkTrackingOptedOut) {
+ this.ctaURLLinkTrackingOptedOut = ctaURLLinkTrackingOptedOut;
+ }
+
+ public HSMQualityScore getQualityScore() {
+ return qualityScore;
+ }
+
+ public void setQualityScore(HSMQualityScore qualityScore) {
+ this.qualityScore = qualityScore;
+ }
+
+ @Override
+ public String toString() {
+ return "WhatsAppTemplateResponse{" +
+ "name='" + name + '\'' +
+ ", language='" + language + '\'' +
+ ", category='" + category + '\'' +
+ ", components=" + components +
+ ", status='" + status + '\'' +
+ ", rejectedReason='" + rejectedReason + '\'' +
+ ", wabaID='" + wabaID + '\'' +
+ ", namespace='" + namespace + '\'' +
+ ", ctaURLLinkTrackingOptedOut='" + ctaURLLinkTrackingOptedOut + '\'' +
+ ", qualityScore='" + qualityScore + '\'' +
+ ", createdAt=" + createdAt +
+ ", updatedAt=" + updatedAt +
+ '}';
+ }
+
+}
diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/SipResponseCode.java b/api/src/main/java/com/messagebird/objects/voicecalls/SipResponseCode.java
new file mode 100644
index 00000000..7061c8fe
--- /dev/null
+++ b/api/src/main/java/com/messagebird/objects/voicecalls/SipResponseCode.java
@@ -0,0 +1,73 @@
+package com.messagebird.objects.voicecalls;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+
+/**
+ * More details, including additional descriptions and common caused can be found here: https://developers.messagebird.com/api/voice-calling/#sip-status-codes
+ * @author leandropinto
+ *
+ */
+public enum SipResponseCode {
+ //Successful
+ OK,
+ //The server understood the request, but is refusing to fulfill it.
+ FORBIDDEN,
+ //The server has definitive information that the user does not exist at the domain specified in the Request-URI.
+ NOT_FOUND,
+ //Couldn't find the user in time.
+ REQUEST_TIMEOUT,
+ //The user existed once, but is not available here any more.
+ GONE,
+ //Callee currently unavailable.
+ TEMPORARILY_UNAVAILABLE,
+ //Request-URI incomplete.
+ ADDRESS_INCOMPLETE,
+ //Callee is busy.
+ BUSY_HERE,
+ //Some aspect of the session description or the Request-URI is not acceptable.
+ NOT_ACCEPTABLE_HERE,
+ //The server could not fulfill the request due to some unexpected condition.
+ INTERNAL_SERVER_ERROR,
+ //The server does not have the ability to fulfill the request, such as because it does not recognize the request method.
+ NOT_IMPLEMENTED,
+ //The server is acting as a gateway or proxy, and received an invalid response from a downstream server while attempting to fulfill the request.
+ BAD_GATEWAY,
+ //The server is undergoing maintenance or is temporarily overloaded and so cannot process the request.
+ SERVICE_UNAVAILABLE;
+
+ @JsonCreator
+ public static SipResponseCode forValue(Integer value) {
+ switch (value) {
+ case 200:
+ return OK;
+ case 403:
+ return FORBIDDEN;
+ case 404:
+ return NOT_FOUND;
+ case 408:
+ return REQUEST_TIMEOUT;
+ case 410:
+ return GONE;
+ case 480:
+ return TEMPORARILY_UNAVAILABLE;
+ case 484:
+ return ADDRESS_INCOMPLETE;
+ case 486:
+ return BUSY_HERE;
+ case 488:
+ return NOT_ACCEPTABLE_HERE;
+ case 500:
+ return INTERNAL_SERVER_ERROR;
+ case 501:
+ return NOT_IMPLEMENTED;
+ case 502:
+ return BAD_GATEWAY;
+ case 503:
+ return SERVICE_UNAVAILABLE;
+
+ default:
+ throw new IllegalArgumentException("Unknown sip response code: " + value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCall.java b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCall.java
index ea7bff21..573b42be 100644
--- a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCall.java
+++ b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCall.java
@@ -1,5 +1,6 @@
package com.messagebird.objects.voicecalls;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.messagebird.objects.MessageBase;
import java.io.Serializable;
@@ -12,9 +13,7 @@ public class VoiceCall implements MessageBase, Serializable {
private String source;
private String destination;
private VoiceCallFlow callFlow;
-
- private String webhookUrl;
- private String webhookToken;
+ private Webhook webhook = new Webhook();
@Override
public String getBody() {
@@ -60,20 +59,43 @@ public void setCallFlow(VoiceCallFlow callFlow) {
this.callFlow = callFlow;
}
+ public Webhook getWebhook() {
+ return webhook;
+ }
+
+ @JsonIgnore
+ public void setWebhook(String url) {
+ this.setWebhook(url, null);
+ }
+
+ @JsonIgnore
+ public void setWebhook(String url, String token) {
+ this.webhook.setUrl(url);
+ this.webhook.setToken(token);
+ }
+
+ @JsonIgnore
+ @Deprecated
public String getWebhookUrl() {
- return webhookUrl;
+ return webhook.getUrl();
}
+ @JsonIgnore
+ @Deprecated
public void setWebhookUrl(String webhookUrl) {
- this.webhookUrl = webhookUrl;
+ this.webhook.setUrl(webhookUrl);
}
+ @JsonIgnore
+ @Deprecated
public String getWebhookToken() {
- return webhookToken;
+ return webhook.getToken();
}
+ @JsonIgnore
+ @Deprecated
public void setWebhookToken(String webhookToken) {
- this.webhookToken = webhookToken;
+ this.webhook.setToken(webhookToken);
}
@Override
@@ -82,8 +104,7 @@ public String toString() {
"source='" + source + '\'' +
", destination='" + destination + '\'' +
", callFlow=" + callFlow +
- ", webhookUrl='" + webhookUrl + '\'' +
- ", webhookToken='" + webhookToken + '\'' +
+ ", webhook=" + webhook +
'}';
}
}
diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlow.java b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlow.java
index d64f0d2c..53130c77 100644
--- a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlow.java
+++ b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlow.java
@@ -23,6 +23,9 @@ public class VoiceCallFlow implements Serializable {
@JsonProperty("default")
private boolean defaultCall;
+ @JsonProperty("maxDuration")
+ private Integer maxDuration;
+
private Date createdAt;
private Date updatedAt;
@@ -37,10 +40,12 @@ public void setId(String id) {
this.id = id;
}
+ @Deprecated
public String getTitle() {
return title;
}
+ @Deprecated
public void setTitle(String title) {
this.title = title;
}
@@ -69,6 +74,10 @@ public void setDefaultCall(boolean defaultCall) {
this.defaultCall = defaultCall;
}
+ public Integer getMaxDuration() { return maxDuration; }
+
+ public void setMaxDuration(Integer maxDuration) { this.maxDuration = maxDuration; }
+
public Date getCreatedAt() {
return createdAt;
}
@@ -101,6 +110,7 @@ public String toString() {
", record=" + record +
", steps=" + steps +
", default=" + defaultCall +
+ ", maxDuration=" + maxDuration +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlowRequest.java b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlowRequest.java
index 3990cfb2..e447d9d9 100644
--- a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlowRequest.java
+++ b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallFlowRequest.java
@@ -36,10 +36,12 @@ public void setId(String id) {
this.id = id;
}
+ @Deprecated
public String getTitle() {
return title;
}
+ @Deprecated
public void setTitle(String title) {
this.title = title;
}
diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallLeg.java b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallLeg.java
index 93dc0fa2..d09b7bdf 100644
--- a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallLeg.java
+++ b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallLeg.java
@@ -28,6 +28,7 @@ public class VoiceCallLeg {
public final Date updatedAt;
public final Date answeredAt;
public final Date endedAt;
+ public final SipResponseCode sipResponseCode;
@JsonCreator
@@ -44,7 +45,8 @@ public VoiceCallLeg(
@JsonProperty("createdAt") Date createdAt,
@JsonProperty("updatedAt") Date updatedAt,
@JsonProperty("answeredAt") Date answeredAt,
- @JsonProperty("endedAt") Date endedAt
+ @JsonProperty("endedAt") Date endedAt,
+ @JsonProperty("sipResponseCode") SipResponseCode sipResponseCode
) {
this.id = id;
this.callID = callID;
@@ -59,6 +61,7 @@ public VoiceCallLeg(
this.updatedAt = updatedAt;
this.answeredAt = answeredAt;
this.endedAt = endedAt;
+ this.sipResponseCode = sipResponseCode;
}
@Override
@@ -77,6 +80,7 @@ public String toString() {
", updatedAt='" + updatedAt + '\'' +
", answeredAt='" + answeredAt + '\'' +
", endedAt='" + endedAt + '\'' +
+ ", sipResponseCode='" + sipResponseCode + '\'' +
'}';
}
@@ -97,11 +101,12 @@ public boolean equals(Object o) {
Objects.equals(createdAt, that.createdAt) &&
Objects.equals(updatedAt, that.updatedAt) &&
Objects.equals(answeredAt, that.answeredAt) &&
- Objects.equals(endedAt, that.endedAt);
+ Objects.equals(endedAt, that.endedAt) &&
+ Objects.equals(sipResponseCode, that.sipResponseCode);
}
@Override
public int hashCode() {
- return Objects.hash(id, callID, source, destination, status, direction, cost, currency, duration, createdAt, updatedAt, answeredAt, endedAt);
+ return Objects.hash(id, callID, source, destination, status, direction, cost, currency, duration, createdAt, updatedAt, answeredAt, endedAt, sipResponseCode);
}
}
diff --git a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallStatus.java b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallStatus.java
index e46c32d9..6806b8e7 100644
--- a/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallStatus.java
+++ b/api/src/main/java/com/messagebird/objects/voicecalls/VoiceCallStatus.java
@@ -5,7 +5,10 @@ public enum VoiceCallStatus {
queued("queued"),
starting("starting"),
ongoing("ongoing"),
- ended("ended");
+ ended("ended"),
+ failed("failed"),
+ busy("busy"),
+ no_answer("no_answer");
final String value;
diff --git a/api/src/test/java/com/messagebird/ContactTest.java b/api/src/test/java/com/messagebird/ContactTest.java
index 2377733d..5dfdea38 100644
--- a/api/src/test/java/com/messagebird/ContactTest.java
+++ b/api/src/test/java/com/messagebird/ContactTest.java
@@ -8,6 +8,7 @@
import org.mockito.Mockito;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
+import static org.junit.Assume.assumeNotNull;
import static org.unitils.reflectionassert.ReflectionAssert.assertReflectionEquals;
/**
@@ -30,6 +31,7 @@ public class ContactTest {
@BeforeClass
public static void setUpClass() throws UnauthorizedException, GeneralException {
String accessKey = System.getProperty("messageBirdAccessKey");
+ assumeNotNull("Integration test skipped: set -DmessageBirdAccessKey to run", accessKey);
msisdn = generateMsisdn();
diff --git a/api/src/test/java/com/messagebird/ConversationMessagesTest.java b/api/src/test/java/com/messagebird/ConversationMessagesTest.java
index dab6e41f..beac543a 100644
--- a/api/src/test/java/com/messagebird/ConversationMessagesTest.java
+++ b/api/src/test/java/com/messagebird/ConversationMessagesTest.java
@@ -1,5 +1,6 @@
package com.messagebird;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.messagebird.exceptions.GeneralException;
import com.messagebird.exceptions.NotFoundException;
import com.messagebird.exceptions.UnauthorizedException;
@@ -22,6 +23,20 @@ public class ConversationMessagesTest {
private static final String JSON_CONVERSATION_MESSAGE_LOCATION = "{\"id\": \"mesid\",\"conversationId\": \"convid\",\"channelId\": \"chanid\",\"status\": \"received\",\"type\": \"location\",\"direction\": \"received\",\"content\": {\"location\": { \"latitude\": 52.344263, \"longitude\": 4.911627 } },\"createdDatetime\": \"2018-08-29T11:49:16Z\",\"updatedDatetime\": \"2018-08-29T11:49:16Z\"}";
private static final String JSON_CONVERSATION_MESSAGE_TEXT = "{\"id\": \"mesid\",\"conversationId\": \"convid\",\"channelId\": \"chanid\",\"status\": \"received\",\"type\": \"text\",\"direction\": \"received\",\"content\": {\"text\": \"Hello\"},\"createdDatetime\": \"2018-08-29T11:49:16Z\",\"updatedDatetime\": \"2018-08-29T11:49:16Z\"}";
private static final String JSON_CONVERSATION_MESSAGE_VIDEO = "{\"id\": \"mesid\",\"conversationId\": \"convid\",\"channelId\": \"chanid\",\"status\": \"received\",\"type\": \"video\",\"direction\": \"received\",\"content\": {\"video\": { \"url\": \"https://example.com/video.mp4\" } },\"createdDatetime\": \"2018-08-29T11:49:16Z\",\"updatedDatetime\": \"2018-08-29T11:49:16Z\"}";
+ private static final String JSON_CONVERSATION_SEND_MESSAGE_RESPONSE = "{\"id\":\"mesid\",\"status\":\"accepted\",\"fallback\":{\"id\":\"mesid\"}}";
+ private static final String JSON_CONVERSATION_MESSAGE_BSUID = "{\"id\": \"mesid\",\"conversationId\": \"convid\",\"channelId\": \"chanid\",\"status\": \"received\",\"type\": \"text\",\"direction\": \"received\",\"content\": {\"text\": \"Hello\"},\"metadata\": {\"sender\": {\"displayName\": \"Alice\",\"username\": \"alice_shop\",\"userId\": \"US.13491208655302741918\",\"parentUserId\": \"US.ENT.11815799212886844830\"},\"receivedAt\": \"2025-04-15T16:00:00Z\"},\"createdDatetime\": \"2025-04-15T16:00:00Z\",\"updatedDatetime\": \"2025-04-15T16:00:00Z\"}";
+ private static final String JSON_STATUS_MESSAGE_METADATA = "{\"id\": \"e5f6a7b8-c9d0-1234-ef01-23456789abcd\",\"from\": \"15551234567\",\"to\": \"US.13491208655302741918\",\"type\": \"text\",\"content\": {\"text\": \"Hello! Your order has been shipped.\"},\"metadata\": {\"sender\": {\"userId\": \"US.13491208655302741918\"},\"receivedAt\": \"0001-01-01T00:00:00Z\"}}";
+
+ private static final String JSON_STATUS_METADATA_WITH_RECIPIENT = "{\"pricing\": {\"billable\": true,\"pricing_model\": \"CBP\",\"category\": \"utility\"},\"conversation\": {\"id\": \"a1b2c3d4\",\"origin\": {\"type\": \"utility\"}},\"biz_opaque_callback_data\": \"order-1234\",\"recipient\": {\"userId\": \"US.13491208655302741918\",\"parentUserId\": \"US.ENT.11815799212886844830\"}}";
+ private static final String JSON_STATUS_METADATA_WITHOUT_RECIPIENT = "{\"pricing\": {\"billable\": true,\"pricing_model\": \"CBP\",\"category\": \"utility\"},\"conversation\": {\"id\": \"a1b2c3d4\",\"origin\": {\"type\": \"utility\"}}}";
+ private static final String JSON_STATUS_METADATA_PARENT_ONLY = "{\"recipient\": {\"parentUserId\": \"US.ENT.11815799212886844830\"}}";
+
+ /**
+ * The same payload as JSON_STATUS_MESSAGE_METADATA with an unrecognised key
+ * added at every nesting level, standing in for fields the platform adds
+ * after this SDK version ships.
+ */
+ private static final String JSON_STATUS_MESSAGE_METADATA_UNKNOWN_FIELDS = "{\"id\": \"e5f6a7b8-c9d0-1234-ef01-23456789abcd\",\"from\": \"15551234567\",\"to\": \"US.13491208655302741918\",\"type\": \"text\",\"futureTopLevelField\": \"ignored\",\"content\": {\"text\": \"Hello! Your order has been shipped.\",\"futureContentField\": \"ignored\"},\"metadata\": {\"sender\": {\"userId\": \"US.13491208655302741918\",\"futureSenderField\": \"ignored\"},\"receivedAt\": \"0001-01-01T00:00:00Z\",\"futureMetadataField\": \"ignored\"}}";
/**
* Epsilon to use when checking two latitudes or longitudes for equality.
@@ -52,6 +67,7 @@ public void testSendConversationMessage() throws GeneralException, UnauthorizedE
conversationMessageRequest.setChannelId("aChannelIdentifier");
conversationMessageRequest.setType(ConversationContentType.VIDEO);
conversationMessageRequest.setContent(conversationContent);
+ conversationMessageRequest.setReportUrl("https://example.com/reportUrl");
MessageBirdService messageBirdService = SpyService
.expects("POST", "conversations/convid/messages", conversationMessageRequest)
@@ -65,6 +81,31 @@ public void testSendConversationMessage() throws GeneralException, UnauthorizedE
assertEquals(ConversationContentType.VIDEO, conversationMessage.getType());
}
+ @Test
+ public void testSendMessage() throws GeneralException, UnauthorizedException {
+ ConversationContent conversationContent = new ConversationContent();
+ conversationContent.setText("test");
+
+ ConversationSendRequest sendRequest = new ConversationSendRequest();
+ sendRequest.setFrom("aChannelIdentifier");
+ sendRequest.setType(ConversationContentType.TEXT);
+ sendRequest.setContent(conversationContent);
+ sendRequest.setReportUrl("https://example.com/reportUrl");
+
+ MessageBirdService messageBirdService = SpyService
+ .expects("POST", "send", sendRequest)
+ .withConversationsAPIBaseURL()
+ .andReturns(new APIResponse(JSON_CONVERSATION_SEND_MESSAGE_RESPONSE));
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService);
+
+ ConversationSendResponse conversationMessage
+ = messageBirdClient.sendMessage(sendRequest);
+
+ assertEquals("mesid", conversationMessage.getId());
+ assertEquals("accepted", conversationMessage.getStatus());
+ assertEquals("mesid", conversationMessage.getFallback().getId());
+ }
+
@Test
public void testViewConversationMessageAudio() throws GeneralException, NotFoundException, UnauthorizedException {
MessageBirdService messageBirdService = SpyService
@@ -156,6 +197,96 @@ public void testViewConversationMessageLocation() throws GeneralException, NotFo
assertEquals(4.911627, location.getLongitude(), EPSILON_LOCATION_EQUALITY);
}
+ @Test
+ public void testViewConversationMessageWithBsuidMetadata() throws GeneralException, NotFoundException, UnauthorizedException {
+ MessageBirdService messageBirdService = SpyService
+ .expects("GET", "messages/mesid")
+ .withConversationsAPIBaseURL()
+ .andReturns(new APIResponse(JSON_CONVERSATION_MESSAGE_BSUID));
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService);
+
+ ConversationMessage message = messageBirdClient.viewConversationMessage("mesid");
+
+ ConversationMessageMetadata metadata = message.getMetadata();
+ assertNotNull(metadata);
+ assertNotNull(metadata.getReceivedAt());
+ ConversationSenderMetadata sender = metadata.getSender();
+ assertEquals("Alice", sender.getDisplayName());
+ assertEquals("alice_shop", sender.getUsername());
+ assertEquals("US.13491208655302741918", sender.getUserId());
+ assertEquals("US.ENT.11815799212886844830", sender.getParentUserId());
+ }
+
+ @Test
+ public void testStatusMessageMetadataToleratesUnknownFields() throws Exception {
+ // A plain mapper, and unknown keys at every level: webhook payload POJOs
+ // must not force consumers to disable FAIL_ON_UNKNOWN_PROPERTIES, and
+ // must survive fields the platform adds after this version ships.
+ ConversationStatusMessageMetadata md = new ObjectMapper().readValue(
+ JSON_STATUS_MESSAGE_METADATA_UNKNOWN_FIELDS, ConversationStatusMessageMetadata.class);
+
+ assertEquals("e5f6a7b8-c9d0-1234-ef01-23456789abcd", md.getId());
+ assertEquals("Hello! Your order has been shipped.", md.getContent().getText());
+ assertEquals("US.13491208655302741918", md.getMetadata().getSender().getUserId());
+ assertNotNull(md.getMetadata().getReceivedAt());
+ }
+
+ @Test
+ public void testStatusMetadataDeserializesRecipient() throws Exception {
+ // A plain mapper: these payload POJOs must not require the caller to
+ // disable FAIL_ON_UNKNOWN_PROPERTIES.
+ ConversationStatusMetadata metadata = new ObjectMapper().readValue(
+ JSON_STATUS_METADATA_WITH_RECIPIENT, ConversationStatusMetadata.class);
+
+ ConversationRecipientMetadata recipient = metadata.getRecipient();
+ assertNotNull(recipient);
+ assertEquals("US.13491208655302741918", recipient.getUserId());
+ assertEquals("US.ENT.11815799212886844830", recipient.getParentUserId());
+
+ // Meta's own objects pass through untouched, snake_case keys and all.
+ assertEquals("CBP", metadata.getPricing().get("pricing_model"));
+ assertEquals("a1b2c3d4", metadata.getConversation().get("id"));
+
+ // Anything else on the payload is kept rather than dropped.
+ assertEquals("order-1234", metadata.getAdditionalProperties().get("biz_opaque_callback_data"));
+ }
+
+ @Test
+ public void testStatusMetadataWithoutRecipientIsNull() throws Exception {
+ ConversationStatusMetadata metadata = new ObjectMapper().readValue(
+ JSON_STATUS_METADATA_WITHOUT_RECIPIENT, ConversationStatusMetadata.class);
+
+ // Accounts that never receive BSUIDs get payloads with no recipient key
+ // at all — the rest of the metadata must still parse.
+ assertNull(metadata.getRecipient());
+ assertEquals("CBP", metadata.getPricing().get("pricing_model"));
+ assertTrue(metadata.getAdditionalProperties().isEmpty());
+ }
+
+ @Test
+ public void testStatusMetadataRecipientWithParentOnly() throws Exception {
+ ConversationStatusMetadata metadata = new ObjectMapper().readValue(
+ JSON_STATUS_METADATA_PARENT_ONLY, ConversationStatusMetadata.class);
+
+ ConversationRecipientMetadata recipient = metadata.getRecipient();
+ assertNotNull(recipient);
+ assertNull(recipient.getUserId());
+ assertEquals("US.ENT.11815799212886844830", recipient.getParentUserId());
+ }
+
+ @Test
+ public void testStatusMessageMetadataDeserializes() throws Exception {
+ ConversationStatusMessageMetadata md = new ObjectMapper().readValue(
+ JSON_STATUS_MESSAGE_METADATA, ConversationStatusMessageMetadata.class);
+
+ assertEquals("e5f6a7b8-c9d0-1234-ef01-23456789abcd", md.getId());
+ assertEquals("15551234567", md.getFrom());
+ assertEquals("US.13491208655302741918", md.getTo());
+ assertEquals("text", md.getType());
+ assertEquals("Hello! Your order has been shipped.", md.getContent().getText());
+ assertEquals("US.13491208655302741918", md.getMetadata().getSender().getUserId());
+ }
+
@Test
public void testViewConversationMessageText() throws GeneralException, NotFoundException, UnauthorizedException {
MessageBirdService messageBirdService = SpyService
diff --git a/api/src/test/java/com/messagebird/ConversationsTest.java b/api/src/test/java/com/messagebird/ConversationsTest.java
index 2f3834d1..4e7624e8 100644
--- a/api/src/test/java/com/messagebird/ConversationsTest.java
+++ b/api/src/test/java/com/messagebird/ConversationsTest.java
@@ -6,6 +6,10 @@
import com.messagebird.objects.conversations.*;
import org.junit.Test;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
import static org.junit.Assert.*;
public class ConversationsTest {
@@ -44,12 +48,19 @@ public void testStartConversation() throws GeneralException, UnauthorizedExcepti
ConversationContent conversationContent = new ConversationContent();
conversationContent.setText("Hello world");
+ Map source = new HashMap<>();
+ source.put("agentId", "abc123");
+ source.put("userId", Arrays.asList(1, 2, 3));
+
ConversationStartRequest request = new ConversationStartRequest(
"31612345678",
ConversationContentType.TEXT,
conversationContent,
- "chanid"
+ "chanid",
+ source,
+ ConversationMessageTag.AccountUpdate
);
+ request.setReportUrl("https://example.com/reportUrl");
MessageBirdService messageBirdService = SpyService
.expects("POST", "conversations/start", request)
@@ -65,7 +76,7 @@ public void testStartConversation() throws GeneralException, UnauthorizedExcepti
@Test
public void testUpdateConversation() throws GeneralException, UnauthorizedException {
MessageBirdService messageBirdService = SpyService
- .expects("PATCH", "conversations/convid", ConversationStatus.ARCHIVED)
+ .expects("PATCH", "conversations/convid", new ConversationUpdateRequest(ConversationStatus.ARCHIVED))
.withConversationsAPIBaseURL()
.andReturns(new APIResponse(JSON_CONVERSATION));
MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService);
diff --git a/api/src/test/java/com/messagebird/MessageBirdClientTest.java b/api/src/test/java/com/messagebird/MessageBirdClientTest.java
index ef8b2af5..b11c2f54 100644
--- a/api/src/test/java/com/messagebird/MessageBirdClientTest.java
+++ b/api/src/test/java/com/messagebird/MessageBirdClientTest.java
@@ -4,19 +4,30 @@
import com.messagebird.exceptions.NotFoundException;
import com.messagebird.exceptions.UnauthorizedException;
import com.messagebird.objects.*;
+import com.messagebird.objects.conversations.*;
+import com.messagebird.objects.integrations.Template;
+import com.messagebird.objects.integrations.TemplateList;
+import com.messagebird.objects.integrations.TemplateResponse;
import com.messagebird.objects.voicecalls.*;
+import java.util.ArrayList;
+
+import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
+import static org.junit.Assume.assumeNotNull;
-import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import static com.messagebird.MessageBirdClient.*;
+import static com.messagebird.TestUtil.*;
+import static com.messagebird.TestUtil.createChildAccountDetailedResponse;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.unitils.reflectionassert.ReflectionAssert.assertReflectionEquals;
@@ -34,7 +45,10 @@ public class MessageBirdClientTest {
@BeforeClass
public static void setUpClass() {
messageBirdAccessKey = System.getProperty("messageBirdAccessKey");
- messageBirdMSISDN = new BigInteger(System.getProperty("messageBirdMSISDN"));
+ String msisdn = System.getProperty("messageBirdMSISDN");
+ assumeNotNull("Integration test skipped: set -DmessageBirdAccessKey and -DmessageBirdMSISDN to run",
+ messageBirdAccessKey, msisdn);
+ messageBirdMSISDN = new BigInteger(msisdn);
}
@Before
@@ -105,6 +119,32 @@ public void testDeleteMessage() throws Exception {
messageBirdClient.deleteMessage("Foo");
}
+ @Test
+ public void testListScheduledMessages() throws Exception {
+ final MessageList mockedResponse = new MessageList();
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock);
+
+ Map filters = new LinkedHashMap<>();
+ filters.put("status", "scheduled");
+
+ when(messageBirdServiceMock.requestList("/messages", filters, null, null, MessageList.class))
+ .thenReturn(mockedResponse);
+
+ final MessageList response = messageBirdClientMock.listMessagesFiltered(null, null, filters);
+ assertNotNull(response);
+ assertEquals(response, mockedResponse);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testListScheduledMessagesWrongFilter() throws Exception {
+ Map filters = new LinkedHashMap<>();
+ filters.put("does not exist", null);
+
+ messageBirdClient.listMessagesFiltered(null, null, filters);
+ }
+
/*********************************************************************/
/** Test message system **/
/*********************************************************************/
@@ -119,7 +159,7 @@ public void testSendDeleteMessage() throws Exception {
assertNotNull(mr.getId());
assertEquals(mr.getReference(), reference);
assertEquals(mr.getBody(), body);
- assertEquals(mr.getDatacoding(), DataCodingType.plain);
+ assertEquals(DataCodingType.plain,mr.getDatacoding());
// Deleting of a message is not yet supported in test mode
// Thread.sleep(1000);
@@ -189,8 +229,8 @@ public void testSendDeleteFlashMessage() throws Exception {
final String body = "Body test message Über € " + messageBirdMSISDN;
final MessageResponse mr = messageBirdClient.sendFlashMessage("originator", body, Collections.singletonList(messageBirdMSISDN));
assertNotNull(mr.getId());
- assertSame(mr.getType(), MsgType.flash);
- assertSame(mr.getMclass(), MClassType.flash);
+ assertSame(MsgType.flash,mr.getType() );
+ assertSame(MClassType.flash, mr.getMclass());
// Deleting of a message is not yet supported in test mode
// Thread.sleep(1000);
@@ -261,8 +301,8 @@ public void testSendVoiceMessage() throws Exception {
final VoiceMessageResponse mr = messageBirdClient.sendVoiceMessage(vm);
assertNotNull(mr.getId());
assertEquals(mr.getBody(), body);
- assertSame(mr.getIfMachine(), IfMachineType.hangup);
- assertSame(mr.getVoice(), VoiceType.male);
+ assertSame(IfMachineType.hangup, mr.getIfMachine() );
+ assertSame(VoiceType.male, mr.getVoice());
// Deleting of a message is not yet supported in test mode
// Thread.sleep(1000);
@@ -289,17 +329,6 @@ public void testSendVoiceMessage2() throws Exception {
assertNotNull(mr.getId());
assertEquals(mr.getBody(), body);
assertEquals(mr.getReference(), reference);
-
- Thread.sleep(500);
- // Viewing of a message is not yet supported in test mode
- // final VoiceMessageResponse mr2 = messageBirdClient.viewVoiceMessage(mr.getId());
- // assertTrue(mr2.getId() != null);
- // assertTrue(mr2.getBody().equals(body));
- // assertTrue(mr2.getReference().equals(reference));
-
- // Deleting of a message is not yet supported in test mode
- // Thread.sleep(1000);
- // Gives 404 messageBirdClient.deleteVoiceMessage(mr.getId());
}
/**
@@ -334,7 +363,7 @@ public void testSendVerifyToken1() throws UnauthorizedException, GeneralExceptio
}
@Test
- public void testSendVerifyTokenAndGetVerifyObject() throws UnauthorizedException, GeneralException, NotFoundException {
+ public void testSendVerifyTokenAndGetVerifyObject() throws UnauthorizedException, GeneralException {
Verify verify = messageBirdClient.sendVerifyToken(messageBirdMSISDN.toString());
assertFalse("href is empty", verify.getHref().isEmpty());
assertFalse("id is empty", verify.getId().isEmpty());
@@ -348,7 +377,7 @@ public void testSendVerifyTokenAndGetVerifyObject() throws UnauthorizedException
}
@Test
- public void testVerifyToken() throws UnauthorizedException, GeneralException, UnsupportedEncodingException {
+ public void testVerifyToken() throws UnauthorizedException, GeneralException {
Verify verify = messageBirdClient.sendVerifyToken(messageBirdMSISDN.toString());
assertFalse("href is empty", verify.getHref().isEmpty());
@@ -364,7 +393,7 @@ public void testVerifyToken() throws UnauthorizedException, GeneralException, Un
}
@Test
- public void testDeleteVerifyToken() throws UnauthorizedException, GeneralException, NotFoundException, UnsupportedEncodingException {
+ public void testDeleteVerifyToken() throws UnauthorizedException, GeneralException {
Verify verify = messageBirdClient.sendVerifyToken(messageBirdMSISDN.toString());
assertFalse("href is empty", verify.getHref().isEmpty());
try {
@@ -402,7 +431,6 @@ public void shouldThrowIllegalArgumentExceptionWhenSourceOfVoiceCallIsMissing()
voiceCall.setDestination("ANY_DESTINATION");
final VoiceCallFlow voiceCallFlow = new VoiceCallFlow();
- voiceCallFlow.setTitle("Test title");
VoiceStep voiceStep = new VoiceStep();
voiceStep.setAction("say");
@@ -418,13 +446,11 @@ public void shouldThrowIllegalArgumentExceptionWhenSourceOfVoiceCallIsMissing()
}
@Test(expected = IllegalArgumentException.class)
- public void shouldThrowIllegalArgumentExceptionWhenDestinationOfVoiceCallIsMissing() throws UnauthorizedException,
- GeneralException {
+ public void shouldThrowIllegalArgumentExceptionWhenDestinationOfVoiceCallIsMissing() throws UnauthorizedException, GeneralException {
final VoiceCall voiceCall = new VoiceCall();
voiceCall.setSource("ANY_SOURCE");
final VoiceCallFlow voiceCallFlow = new VoiceCallFlow();
- voiceCallFlow.setTitle("Test title");
VoiceStep voiceStep = new VoiceStep();
voiceStep.setAction("say");
@@ -471,8 +497,7 @@ public void testViewVoiceCall() throws UnauthorizedException, GeneralException,
}
@Test
- public void testDeleteVoiceCall() throws UnauthorizedException,
- GeneralException, NotFoundException {
+ public void testDeleteVoiceCall() throws UnauthorizedException, GeneralException, NotFoundException {
MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
@@ -769,4 +794,827 @@ public void testDeleteWebhook() throws NotFoundException, GeneralException, Unau
messageBirdClientMock.deleteWebhook("id");
verify(messageBirdServiceMock, times(1)).deleteByID(VOICE_CALLS_BASE_URL + WEBHOOKS, "id");
}
+
+ @Test
+ public void testListNumbersForPurchase() throws IllegalArgumentException, GeneralException, UnauthorizedException, NotFoundException {
+ final String url = String.format("%s/available-phone-numbers", NUMBERS_CALLS_BASE_URL);
+ final PhoneNumbersResponse mockedResponse = new PhoneNumbersResponse();
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock);
+
+ when(messageBirdServiceMock.requestByID(url, "NL", PhoneNumbersResponse.class))
+ .thenReturn(mockedResponse);
+
+ final PhoneNumbersResponse response = messageBirdClientMock.listNumbersForPurchase("NL");
+ verify(messageBirdServiceMock, times(1)).requestByID(url, "NL", PhoneNumbersResponse.class);
+
+ assertNotNull(response);
+ assertEquals(response, mockedResponse);
+ }
+
+ @Test
+ public void testListNumbersForPurchaseWithParams() throws IllegalArgumentException, GeneralException, UnauthorizedException, NotFoundException {
+ final String url = String.format("%s/available-phone-numbers", NUMBERS_CALLS_BASE_URL);
+ final PhoneNumbersResponse mockedResponse = new PhoneNumbersResponse();
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock);
+
+ PhoneNumbersLookup options = new PhoneNumbersLookup();
+ options.setFeatures(PhoneNumberFeature.VOICE, PhoneNumberFeature.SMS);
+ options.setType(PhoneNumberType.MOBILE);
+ options.setLimit(1);
+ options.setNumber(562);
+ options.setSearchPattern(PhoneNumberSearchPattern.START);
+
+ when(messageBirdServiceMock.requestByID(url, "US", options.toHashMap(), PhoneNumbersResponse.class))
+ .thenReturn(mockedResponse);
+
+ final PhoneNumbersResponse response = messageBirdClientMock.listNumbersForPurchase("US", options);
+ verify(messageBirdServiceMock, times(1)).requestByID(url, "US", options.toHashMap(), PhoneNumbersResponse.class);
+ assertNotNull(response);
+ assertEquals(response, mockedResponse);
+ }
+
+ @Test
+ public void testPurchaseNumber() throws UnauthorizedException, GeneralException {
+ final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
+
+ PurchasedNumberCreatedResponse purchasedNumberMockData = new PurchasedNumberCreatedResponse();
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock);
+
+ final Map payload = new LinkedHashMap();
+ payload.put("number", "15625267429");
+ payload.put("countryCode", "US");
+ payload.put("billingIntervalMonths", 1);
+
+ when(messageBirdServiceMock.sendPayLoad(url, payload, PurchasedNumberCreatedResponse.class))
+ .thenReturn(purchasedNumberMockData);
+ final PurchasedNumberCreatedResponse response = messageBirdClientMock.purchaseNumber("15625267429", "US", 1);
+ verify(messageBirdServiceMock, times(1)).sendPayLoad(url, payload, PurchasedNumberCreatedResponse.class);
+ assertNotNull(response);
+ assertEquals(response, purchasedNumberMockData);
+ }
+
+ @Test
+ public void testListPurchasedNumbers() throws UnauthorizedException, GeneralException, NotFoundException {
+ final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
+
+ PurchasedNumbersResponse purchasedNumbersMockData = new PurchasedNumbersResponse();
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock);
+
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+ filter.setLimit(1);
+ filter.addFeature(PhoneNumberFeature.SMS);
+ filter.setType(PhoneNumberType.MOBILE);
+ filter.addTag("tag");
+
+ when(messageBirdServiceMock.requestByID(url, null, filter.toHashMap(), PurchasedNumbersResponse.class))
+ .thenReturn(purchasedNumbersMockData);
+
+ final PurchasedNumbersResponse response = messageBirdClientMock.listPurchasedNumbers(filter);
+
+ verify(messageBirdServiceMock, times(1)).requestByID(url, null, filter.toHashMap(), PurchasedNumbersResponse.class);
+ assertNotNull(response);
+ assertEquals(response, purchasedNumbersMockData);
+ }
+
+ @Test
+ public void testViewPurchasedNumber() throws UnauthorizedException, GeneralException, NotFoundException {
+ final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
+
+ PurchasedNumber purchasedNumberMockData = new PurchasedNumber();
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock);
+ when(messageBirdServiceMock.requestByID(url, "15625267429", PurchasedNumber.class))
+ .thenReturn(purchasedNumberMockData);
+ final PurchasedNumber response = messageBirdClientMock.viewPurchasedNumber("15625267429");
+
+ verify(messageBirdServiceMock, times(1)).requestByID(url, "15625267429", PurchasedNumber.class);
+ assertNotNull(response);
+ assertEquals(response, purchasedNumberMockData);
+ }
+
+ @Test
+ public void updatePurchasedNumber() throws UnauthorizedException, GeneralException {
+ final String phoneNumber = "15625267429";
+ final String url = String.format("%s/phone-numbers/%s", NUMBERS_CALLS_BASE_URL, phoneNumber);
+
+ PurchasedNumber updatedNumberMock = new PurchasedNumber();
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock);
+
+ final Map> payload = new HashMap>();
+ payload.put("tags", Collections.singletonList("tag"));
+
+ when(messageBirdServiceMock.sendPayLoad("PATCH", url, payload, PurchasedNumber.class))
+ .thenReturn(updatedNumberMock);
+ final PurchasedNumber response = messageBirdClientMock.updateNumber(phoneNumber, "tag");
+ verify(messageBirdServiceMock, times(1)).sendPayLoad("PATCH", url, payload, PurchasedNumber.class);
+ assertNotNull(response);
+ assertEquals(response, updatedNumberMock);
+ }
+
+ @Test
+ public void deletePurchasedNumber() throws UnauthorizedException, GeneralException, NotFoundException {
+ final String phoneNumber = "15625267429";
+ final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock);
+
+ messageBirdClientMock.cancelNumber(phoneNumber);
+ verify(messageBirdServiceMock, times(1)).deleteByID(url, phoneNumber);
+ }
+
+ @Test
+ public void testDeleteRecording() throws NotFoundException, GeneralException, UnauthorizedException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientMock = new MessageBirdClient(messageBirdServiceMock);
+ String url = String.format(
+ "%s%s/%s%s/%s%s",
+ VOICE_CALLS_BASE_URL,
+ VOICECALLSPATH,
+ "ANY_CALL_ID",
+ LEGSPATH,
+ "ANY_LEG_ID",
+ RECORDINGPATH
+ );
+ messageBirdClientMock.deleteRecording("ANY_CALL_ID", "ANY_LEG_ID","recordingID");
+ verify(messageBirdServiceMock, times(1)).deleteByID(url , "recordingID");
+ }
+
+ @Test
+ public void testMockUploadFile() throws GeneralException, UnauthorizedException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
+ byte[] binary = {1, 2, 3, 4, 5, 6};
+ String contentType = "image/png";
+ String filename = "filename.png";
+ messageBirdClient.uploadFile(binary, contentType, filename);
+ String url = MESSAGING_BASE_URL + FILES_PATH;
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", contentType);
+ headers.put("filename", filename);
+ verify(messageBirdServiceMock, times(1)).sendPayLoad("POST", url, headers, binary, FileUploadResponse.class);
+ }
+
+ @Test
+ public void testUploadFileWithNullBinary() {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
+ String contentType = "image/png";
+ String filename = "filename.png";
+ assertThrows(IllegalArgumentException.class, () -> messageBirdClient.uploadFile(null, contentType, filename));
+ }
+
+ @Test
+ public void testUploadFileWithNullContentType() {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
+ byte[] binary = {1, 2, 3, 4, 5, 6};
+ String filename = "filename.png";
+ assertThrows(IllegalArgumentException.class, () -> messageBirdClient.uploadFile(binary, null, filename));
+ }
+
+ @Test
+ public void testUploadFileWithNullFilename() throws GeneralException, UnauthorizedException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
+ byte[] binary = {1, 2, 3, 4, 5, 6};
+ String contentType = "image/png";
+ messageBirdClient.uploadFile(binary, contentType, null);
+ String url = MESSAGING_BASE_URL + FILES_PATH;
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", contentType);
+ verify(messageBirdServiceMock, times(1)).sendPayLoad("POST", url, headers, binary, FileUploadResponse.class);
+ }
+
+ @Test
+ public void testMockDownloadFile() throws GeneralException, UnauthorizedException, NotFoundException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
+ String id = "8144d3bf-6228-4b0e-903f-022d8917f297";
+ String filename = "file.png";
+ String basePath = "/base/path";
+ messageBirdClient.downloadFile(id, filename, basePath);
+ String url = MESSAGING_BASE_URL + FILES_PATH + "/" + id;
+ verify(messageBirdServiceMock, times(1)).getBinaryData(url, basePath, filename);
+ }
+
+ @Test
+ public void testDownloadFileWithNullId() {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
+ String filename = "file.png";
+ String basePath = "/base/path";
+ assertThrows(IllegalArgumentException.class, () -> messageBirdClient.downloadFile(null, filename, basePath));
+ }
+
+ @Test
+ public void testDownloadFileWithNullFilename() throws GeneralException, UnauthorizedException, NotFoundException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
+ String id = "8144d3bf-6228-4b0e-903f-022d8917f297";
+ String basePath = "/base/path";
+ messageBirdClient.downloadFile(id, null, basePath);
+ String url = MESSAGING_BASE_URL + FILES_PATH + "/" + id;
+ verify(messageBirdServiceMock, times(1)).getBinaryData(url, basePath, id);
+ }
+
+ @Test
+ public void testDownloadFileWithNullBasePath() throws GeneralException, UnauthorizedException, NotFoundException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
+ String id = "8144d3bf-6228-4b0e-903f-022d8917f297";
+ String filename = "file.png";
+ messageBirdClient.downloadFile(id, filename, null);
+ String url = MESSAGING_BASE_URL + FILES_PATH + "/" + id;
+ verify(messageBirdServiceMock, times(1)).getBinaryData(url, null, filename);
+ }
+
+ /****************************************************************************************************/
+ /** Testing WhatsApp Templates **/
+ /****************************************************************************************************/
+
+ @Test
+ public void testCreateWhatsAppTemplate() throws UnauthorizedException, GeneralException {
+ final TemplateResponse templateResponse = TestUtil.createWhatsAppTemplateResponse("sample_template_name", "ko");
+ final Template template = TestUtil.createWhatsAppTemplate("sample_template_name", "ko");
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+
+ when(messageBirdServiceMock.sendPayLoad(url, template, TemplateResponse.class))
+ .thenReturn(templateResponse);
+
+ final TemplateResponse response = messageBirdClientInjectMock.createWhatsAppTemplate(template);
+
+ verify(messageBirdServiceMock, times(1)).sendPayLoad(url, template, TemplateResponse.class);
+ assertNotNull(response);
+ assertEquals(response.getName(), templateResponse.getName());
+ assertEquals(response.getLanguage(), templateResponse.getLanguage());
+ assertEquals(response.getCategory(), templateResponse.getCategory());
+ assertEquals(response.getStatus(), templateResponse.getStatus());
+ assertEquals(response.getWabaID(), templateResponse.getWabaID());
+ assertEquals(response.getCreatedAt(), templateResponse.getCreatedAt());
+ assertEquals(response.getUpdatedAt(), templateResponse.getUpdatedAt());
+
+ /* verify components */
+ for (int i = 0; i < response.getComponents().size(); i++) {
+ assertEquals(response.getComponents().get(i).getType(), templateResponse.getComponents().get(i).getType());
+ assertEquals(response.getComponents().get(i).getFormat(), templateResponse.getComponents().get(i).getFormat());
+ assertEquals(response.getComponents().get(i).getText(), templateResponse.getComponents().get(i).getText());
+ }
+ }
+ @Test
+ public void testCreateWhatsAppCarouselTemplate() throws UnauthorizedException, GeneralException {
+ final TemplateResponse templateResponse = TestUtil.createWhatsAppCarouselTemplateResponse("sample_template_name", "ko");
+ final Template template = TestUtil.createWhatsAppCarouselTemplate("sample_template_name", "ko");
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+
+ when(messageBirdServiceMock.sendPayLoad(url, template, TemplateResponse.class))
+ .thenReturn(templateResponse);
+
+ final TemplateResponse response = messageBirdClientInjectMock.createWhatsAppTemplate(template);
+
+ verify(messageBirdServiceMock, times(1)).sendPayLoad(url, template, TemplateResponse.class);
+ assertNotNull(response);
+ assertEquals(response.getName(), templateResponse.getName());
+ assertEquals(response.getLanguage(), templateResponse.getLanguage());
+ assertEquals(response.getCategory(), templateResponse.getCategory());
+ assertEquals(response.getStatus(), templateResponse.getStatus());
+ assertEquals(response.getWabaID(), templateResponse.getWabaID());
+ assertEquals(response.getCreatedAt(), templateResponse.getCreatedAt());
+ assertEquals(response.getUpdatedAt(), templateResponse.getUpdatedAt());
+
+ /* verify components */
+ for (int i = 0; i < response.getComponents().size(); i++) {
+ assertEquals(response.getComponents().get(i).getType(), templateResponse.getComponents().get(i).getType());
+ assertEquals(response.getComponents().get(i).getFormat(), templateResponse.getComponents().get(i).getFormat());
+ assertEquals(response.getComponents().get(i).getText(), templateResponse.getComponents().get(i).getText());
+ }
+ }
+
+ @Test
+ public void testUpdateWhatsAppTemplate() throws UnauthorizedException, GeneralException {
+ final TemplateResponse templateResponse = TestUtil.createWhatsAppTemplateResponse("sample_template_name", "ko");
+ final Template template = TestUtil.createWhatsAppTemplate("sample_template_name", "ko");
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s/%s/%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ "sample_template_name",
+ "ko"
+ );
+
+ when(messageBirdServiceMock.sendPayLoad("PUT",url, template, TemplateResponse.class))
+ .thenReturn(templateResponse);
+
+ final TemplateResponse response = messageBirdClientInjectMock.updateWhatsAppTemplate(template,"sample_template_name","ko");
+ verify(messageBirdServiceMock, times(1)).sendPayLoad("PUT",url, template, TemplateResponse.class);
+ assertNotNull(response);
+ assertEquals(response.getName(), templateResponse.getName());
+ assertEquals(response.getLanguage(), templateResponse.getLanguage());
+ assertEquals(response.getCategory(), templateResponse.getCategory());
+ assertEquals(response.getStatus(), templateResponse.getStatus());
+ assertEquals(response.getWabaID(), templateResponse.getWabaID());
+ assertEquals(response.getCreatedAt(), templateResponse.getCreatedAt());
+ assertEquals(response.getUpdatedAt(), templateResponse.getUpdatedAt());
+
+ /* verify components */
+ for (int i = 0; i < response.getComponents().size(); i++) {
+ assertEquals(response.getComponents().get(i).getType(), templateResponse.getComponents().get(i).getType());
+ assertEquals(response.getComponents().get(i).getFormat(), templateResponse.getComponents().get(i).getFormat());
+ assertEquals(response.getComponents().get(i).getText(), templateResponse.getComponents().get(i).getText());
+ }
+ }
+
+ @Test
+ public void testListWhatsAppTemplates() throws UnauthorizedException, GeneralException {
+ final TemplateList templateList = TestUtil.createWhatsAppTemplateList("sample_template_name");
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V3,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+
+ when(messageBirdServiceMock.requestList(url, 0, 0, TemplateList.class))
+ .thenReturn(templateList);
+
+ final TemplateList response = messageBirdClientInjectMock.listWhatsAppTemplates(0, 0);
+ verify(messageBirdServiceMock, times(1)).requestList(url, 0, 0, TemplateList.class);
+ assertNotNull(response);
+ for(int i = 0; i < response.getItems().size() ; i++) {
+ assertReflectionEquals(response.getItems().get(i), templateList.getItems().get(i));
+ }
+ }
+
+ @Test
+ public void testListWhatsAppTemplatesDefault() throws UnauthorizedException, GeneralException {
+ final TemplateList templateList = TestUtil.createWhatsAppTemplateList("sample_template_name");
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V3,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+
+ when(messageBirdServiceMock.requestList(url, 0, 10, TemplateList.class))
+ .thenReturn(templateList);
+
+ final TemplateList response = messageBirdClientInjectMock.listWhatsAppTemplates();
+ verify(messageBirdServiceMock, times(1)).requestList(url, 0, 10, TemplateList.class);
+ assertNotNull(response);
+ for(int i = 0; i < response.getItems().size() ; i++) {
+ assertReflectionEquals(response.getItems().get(i), templateList.getItems().get(i));
+ }
+ }
+
+ @Test
+ public void testListWhatsAppTemplatesByWABAID() throws UnauthorizedException, GeneralException {
+ final TemplateList templateList = TestUtil.createWhatsAppTemplateList("sample_template_name");
+ final String wabaID = "testWABAID";
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V3,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+
+ Map map = new LinkedHashMap<>();
+ map.put("wabaId", wabaID);
+
+ when(messageBirdServiceMock.requestList(url, map, 0, 0, TemplateList.class))
+ .thenReturn(templateList);
+
+ final TemplateList response = messageBirdClientInjectMock.listWhatsAppTemplates(0, 0, wabaID, null);
+ verify(messageBirdServiceMock, times(1)).requestList(url, map, 0, 0, TemplateList.class);
+ assertNotNull(response);
+ for(int i = 0; i < response.getItems().size() ; i++) {
+ assertReflectionEquals(response.getItems().get(i), templateList.getItems().get(i));
+ }
+ }
+
+ @Test
+ public void testListWhatsAppTemplatesByChannelID() throws UnauthorizedException, GeneralException {
+ final TemplateList templateList = TestUtil.createWhatsAppTemplateList("sample_template_name");
+ final String channelID = "channel-id";
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V3,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+
+ Map map = new LinkedHashMap<>();
+ map.put("channelId", channelID);
+
+ when(messageBirdServiceMock.requestList(url, map, 0, 0, TemplateList.class))
+ .thenReturn(templateList);
+
+ final TemplateList response = messageBirdClientInjectMock.listWhatsAppTemplates(0, 0, null, channelID);
+ verify(messageBirdServiceMock, times(1)).requestList(url, map, 0, 0, TemplateList.class);
+ assertNotNull(response);
+ for(int i = 0; i < response.getItems().size() ; i++) {
+ assertReflectionEquals(response.getItems().get(i), templateList.getItems().get(i));
+ }
+ }
+
+ @Test
+ public void testGetWhatsAppTemplatesBy() throws GeneralException, UnauthorizedException, NotFoundException {
+ final String templateName = "sample_template_name";
+ final TemplateResponse templateResponse1 = TestUtil.createWhatsAppTemplateResponse(templateName, "en_US");
+ final TemplateResponse templateResponse2 = TestUtil.createWhatsAppTemplateResponse("another_template", "en_US");
+ final List templateList = new ArrayList<>();
+ templateList.add(templateResponse1);
+ templateList.add(templateResponse2);
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+
+ when(messageBirdServiceMock.requestByIdAsList(url, templateName, TemplateResponse.class))
+ .thenReturn(templateList);
+
+ final List response = messageBirdClientInjectMock.getWhatsAppTemplatesBy(templateName);
+ verify(messageBirdServiceMock, times(1)).requestByIdAsList(url, templateName, TemplateResponse.class);
+ assertNotNull(response);
+ assertEquals(response.size(), templateList.size());
+ for(int i = 0; i < response.size() ; i++) {
+ assertReflectionEquals(response.get(i), templateList.get(i));
+ }
+ }
+
+ @Test
+ public void testGetWhatsAppTemplatesByNameAndWABAID() throws GeneralException, UnauthorizedException, NotFoundException {
+ final String templateName = "sample_template_name";
+ final String wabaID = "testWABAID";
+ final TemplateResponse templateResponse1 = TestUtil.createWhatsAppTemplateResponse(templateName, "en_US");
+ final TemplateResponse templateResponse2 = TestUtil.createWhatsAppTemplateResponse("another_template", "en_US");
+ final List templateList = new ArrayList<>();
+ templateList.add(templateResponse1);
+ templateList.add(templateResponse2);
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+ String id = String.format(
+ "%s?wabaId=%s",
+ templateName,
+ wabaID
+ );
+
+ when(messageBirdServiceMock.requestByIdAsList(url, id, TemplateResponse.class))
+ .thenReturn(templateList);
+
+ final List response = messageBirdClientInjectMock.getWhatsAppTemplatesBy(templateName, wabaID, null);
+ verify(messageBirdServiceMock, times(1)).requestByIdAsList(url, id, TemplateResponse.class);
+ assertNotNull(response);
+ assertEquals(response.size(), templateList.size());
+ for(int i = 0; i < response.size() ; i++) {
+ assertReflectionEquals(response.get(i), templateList.get(i));
+ }
+ }
+
+ @Test
+ public void testGetWhatsAppTemplatesByNameForChannelID() throws GeneralException, UnauthorizedException, NotFoundException {
+ final String templateName = "sample_template_name";
+ final String channelID = "channel-id";
+ final TemplateResponse templateResponse1 = TestUtil.createWhatsAppTemplateResponse(templateName, "en_US");
+ final TemplateResponse templateResponse2 = TestUtil.createWhatsAppTemplateResponse("another_template", "en_US");
+ final List templateList = new ArrayList<>();
+ templateList.add(templateResponse1);
+ templateList.add(templateResponse2);
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH
+ );
+ String id = String.format(
+ "%s?channelId=%s",
+ templateName,
+ channelID
+ );
+
+ when(messageBirdServiceMock.requestByIdAsList(url, id, TemplateResponse.class))
+ .thenReturn(templateList);
+
+ final List response = messageBirdClientInjectMock.getWhatsAppTemplatesBy(templateName, null, channelID);
+ verify(messageBirdServiceMock, times(1)).requestByIdAsList(url, id, TemplateResponse.class);
+ assertNotNull(response);
+ assertEquals(response.size(), templateList.size());
+ for(int i = 0; i < response.size() ; i++) {
+ assertReflectionEquals(response.get(i), templateList.get(i));
+ }
+ }
+
+ @Test
+ public void testFetchWhatsAppTemplateBy() throws UnauthorizedException, GeneralException, NotFoundException {
+ final String templateName = "sample_template_name";
+ final String language = "ko";
+ final TemplateResponse template = TestUtil.createWhatsAppTemplateResponse(templateName, language);
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s/%s/%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ templateName,
+ language
+ );
+
+ when(messageBirdServiceMock.request(url, TemplateResponse.class))
+ .thenReturn(template);
+
+ final TemplateResponse response = messageBirdClientInjectMock.fetchWhatsAppTemplateBy(templateName, language);
+ verify(messageBirdServiceMock, times(1)).request(url, TemplateResponse.class);
+ assertNotNull(response);
+ assertEquals(response.getName(), template.getName());
+ assertEquals(response.getLanguage(), template.getLanguage());
+ assertEquals(response.getStatus(), template.getStatus());
+ assertReflectionEquals(response.getComponents(), template.getComponents());
+ }
+
+ @Test
+ public void testFetchWhatsAppTemplateByNameAndLanguageAndWABAID() throws UnauthorizedException, GeneralException, NotFoundException {
+ final String templateName = "sample_template_name";
+ final String language = "ko";
+ final String wabaID = "testWABAID";
+ final TemplateResponse template = TestUtil.createWhatsAppTemplateResponse(templateName, language);
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s/%s/%s?wabaId=%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ templateName,
+ language,
+ wabaID
+ );
+
+ when(messageBirdServiceMock.request(url, TemplateResponse.class))
+ .thenReturn(template);
+
+ final TemplateResponse response = messageBirdClientInjectMock.fetchWhatsAppTemplateBy(templateName, language, wabaID, null);
+ verify(messageBirdServiceMock, times(1)).request(url, TemplateResponse.class);
+ assertNotNull(response);
+ assertEquals(response.getName(), template.getName());
+ assertEquals(response.getLanguage(), template.getLanguage());
+ assertEquals(response.getStatus(), template.getStatus());
+ assertReflectionEquals(response.getComponents(), template.getComponents());
+ }
+
+ @Test
+ public void testFetchWhatsAppTemplateByNameAndLanguageForChannelID() throws UnauthorizedException, GeneralException, NotFoundException {
+ final String templateName = "sample_template_name";
+ final String language = "ko";
+ final String channelID = "channel-id";
+ final TemplateResponse template = TestUtil.createWhatsAppTemplateResponse(templateName, language);
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s/%s/%s?channelId=%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ templateName,
+ language,
+ channelID
+ );
+
+ when(messageBirdServiceMock.request(url, TemplateResponse.class))
+ .thenReturn(template);
+
+ final TemplateResponse response = messageBirdClientInjectMock.fetchWhatsAppTemplateBy(templateName, language, null, channelID);
+ verify(messageBirdServiceMock, times(1)).request(url, TemplateResponse.class);
+ assertNotNull(response);
+ assertEquals(response.getName(), template.getName());
+ assertEquals(response.getLanguage(), template.getLanguage());
+ assertEquals(response.getStatus(), template.getStatus());
+ assertReflectionEquals(response.getComponents(), template.getComponents());
+ }
+
+ @Test
+ public void testDeleteTemplatesByName() throws UnauthorizedException, GeneralException, NotFoundException {
+ final String templateName = "sample_template_name";
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ String url = String.format(
+ "%s%s%s/%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ templateName
+ );
+
+ when(messageBirdServiceMock.delete(url, null)).thenReturn(null);
+ messageBirdClientInjectMock.deleteTemplatesBy(templateName);
+ verify(messageBirdServiceMock).delete(url, null);
+ }
+
+ @Test
+ public void testCreateChildAccounts() throws GeneralException, UnauthorizedException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+ ChildAccountCreateResponse childAccountCreateResponse = createChildAccountCreateResponse();
+ ChildAccountRequest childAccountRequest = new ChildAccountRequest();
+ childAccountRequest.setName("name");
+ when(messageBirdServiceMock.sendPayLoad(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts" , childAccountRequest, ChildAccountCreateResponse.class))
+ .thenReturn(childAccountCreateResponse);
+ final ChildAccountCreateResponse response = messageBirdClientInjectMock.createChildAccount(childAccountRequest);
+
+ verify(messageBirdServiceMock, times(1))
+ .sendPayLoad(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts" , childAccountRequest, ChildAccountCreateResponse.class);
+ assertNotNull(response);
+ assertEquals(response.getId(), childAccountCreateResponse.getId());
+ assertEquals(response.getName(), childAccountCreateResponse.getName());
+ assertEquals(response.getInvoiceAggregation(), childAccountCreateResponse.getInvoiceAggregation());
+ assertEquals(response.getSigningKey(), childAccountCreateResponse.getSigningKey());
+ assertEquals(response.getAccessKeys().get(0).getId(), childAccountCreateResponse.getAccessKeys().get(0).getId());
+ assertEquals(response.getAccessKeys().get(0).getAccessKey(), childAccountCreateResponse.getAccessKeys().get(0).getAccessKey());
+ assertEquals(response.getAccessKeys().get(0).getMode(), childAccountCreateResponse.getAccessKeys().get(0).getMode());
+ }
+
+ @Test
+ public void testGetChildAccount() throws GeneralException, UnauthorizedException, NotFoundException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+ ChildAccountDetailedResponse childAccountDetailedResponse = createChildAccountDetailedResponse();
+ when(messageBirdServiceMock.requestByID(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", "ANY_ID", ChildAccountDetailedResponse.class))
+ .thenReturn(childAccountDetailedResponse);
+ ChildAccountDetailedResponse response = messageBirdClientInjectMock.getChildAccountById("ANY_ID");
+
+ verify(messageBirdServiceMock, times(1))
+ .requestByID(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", "ANY_ID", ChildAccountDetailedResponse.class);
+ assertNotNull(response);
+ assertEquals(response.getId(), childAccountDetailedResponse.getId());
+ assertEquals(response.getName(), childAccountDetailedResponse.getName());
+ assertEquals(response.getInvoiceAggregation(), childAccountDetailedResponse.getInvoiceAggregation());
+ }
+
+ @Test
+ public void testGetChildAccounts() throws GeneralException, UnauthorizedException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+ List childAccountResponses = Collections.singletonList(createChildAccountResponse());
+ when(messageBirdServiceMock.requestList(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", 1, 10, List.class))
+ .thenReturn(childAccountResponses);
+
+ List response = messageBirdClientInjectMock.getChildAccounts(1, 10);
+
+ verify(messageBirdServiceMock, times(1))
+ .requestList(PARTNER_ACCOUNTS_BASE_URL + "/child-accounts", 1, 10, List.class);
+ assertNotNull(response);
+ assertEquals(response.get(0).getId(), childAccountResponses.get(0).getId());
+ assertEquals(response.get(0).getName(), childAccountResponses.get(0).getName());
+ }
+
+ @Test
+ public void testUpdateChildAccount() throws GeneralException, UnauthorizedException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+ ChildAccountResponse childAccountResponse = createChildAccountResponse();
+ when(messageBirdServiceMock.sendPayLoad(any(), any(), any(), any()))
+ .thenReturn(childAccountResponse);
+ ChildAccountResponse response = messageBirdClientInjectMock.updateChildAccount("ANY_NAME", "ANY_ID");
+
+ verify(messageBirdServiceMock, times(1))
+ .sendPayLoad(any(), any(), any(), any());
+ assertNotNull(response);
+ assertEquals(response.getId(), childAccountResponse.getId());
+ assertEquals(response.getName(), childAccountResponse.getName());
+ }
+
+ @Test
+ public void testDeleteChildAccount() throws GeneralException, UnauthorizedException, NotFoundException {
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+ String url = String.format("%s/child-accounts/%s", PARTNER_ACCOUNTS_BASE_URL, "ANY_ID");
+ doNothing().when(messageBirdServiceMock).deleteByID(url, "ANY_ID");
+ messageBirdClientInjectMock.deleteChildAccount("id");
+ }
+
+ @Test
+ public void testConversationMessage() throws Exception {
+ ConversationSendRequest request = createDummyConversationRequest();
+ ConversationSendResponse conversationSendResponse = new ConversationSendResponse();
+ conversationSendResponse.setStatus("ACCEPTED");
+ conversationSendResponse.setId("1234");
+
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+
+ when(messageBirdServiceMock.sendPayLoad( CONVERSATIONS_BASE_URL + CONVERSATION_SEND_PATH, request, ConversationSendResponse.class))
+ .thenReturn(conversationSendResponse);
+ ConversationSendResponse response = messageBirdClientInjectMock.sendMessage(request);
+
+ verify(messageBirdServiceMock, times(1))
+ .sendPayLoad(CONVERSATIONS_BASE_URL + CONVERSATION_SEND_PATH, request, ConversationSendResponse.class);
+ assertNotNull(response.getId());
+ assertNotNull(response.getStatus());
+ }
+
+ private ConversationSendRequest createDummyConversationRequest() {
+ ConversationContent conversationContent = new ConversationContent();
+ conversationContent.setText("test");
+ ConversationSendRequest request = new ConversationSendRequest();
+ request.setFrom("channelid");
+ request.setTo("+34123123123");
+ request.setTtl("15s");
+ request.setType(ConversationContentType.TEXT);
+ request.setContent(conversationContent);
+ request.setTrackId("mycampaign");
+ return request;
+ }
+
+ @Test
+ public void testUnpauseTemplatesByTemplateName_Success() throws UnauthorizedException, GeneralException {
+ final Template template = TestUtil.createWhatsAppTemplate("sample_template_name", "ko");
+ MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
+ MessageBirdClient messageBirdClientInjectMock = new MessageBirdClient(messageBirdServiceMock);
+ String url = String.format(
+ "%s%s%s%s/%s",
+ INTEGRATIONS_BASE_URL_V2,
+ INTEGRATIONS_WHATSAPP_PATH,
+ TEMPLATES_PATH,
+ UNPAUSE_TEMAPLATE_PATH,
+ "sample_template_name"
+ );
+ messageBirdClientInjectMock.unpauseTemplatesByTemplateName("sample_template_name");
+ verify(messageBirdServiceMock).sendPayLoad("POST", url, "", null);
+ }
+
+ @Test(expected = GeneralException.class)
+ public void testUnpauseTemplatesByTemplateName_NotFound() throws UnauthorizedException, GeneralException {
+ messageBirdClient.unpauseTemplatesByTemplateName("foo");
+ }
}
diff --git a/api/src/test/java/com/messagebird/OutboundSmsPricesTest.java b/api/src/test/java/com/messagebird/OutboundSmsPricesTest.java
new file mode 100644
index 00000000..665dba71
--- /dev/null
+++ b/api/src/test/java/com/messagebird/OutboundSmsPricesTest.java
@@ -0,0 +1,83 @@
+package com.messagebird;
+
+import com.messagebird.exceptions.GeneralException;
+import com.messagebird.exceptions.NotFoundException;
+import com.messagebird.exceptions.UnauthorizedException;
+import com.messagebird.objects.OutboundSmsPriceResponse;
+import com.messagebird.util.Resources;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+
+public class OutboundSmsPricesTest {
+
+ @Test
+ public void testGetOutboundSmsPrices() throws GeneralException, UnauthorizedException, NotFoundException {
+ String responseFixture = Resources.readResourceText("/fixtures/outbound_sms_prices.json");
+
+ MessageBirdService messageBirdService = SpyService
+ .expects("GET", "pricing/sms/outbound")
+ .withRestAPIBaseURL()
+ .andReturns(new APIResponse(responseFixture, 200));
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService);
+
+ assertReceivedExpectedResponse(messageBirdClient.getOutboundSmsPrices());
+ }
+
+ @Test
+ public void testGetOutboundSmsPricesSmppUsername() throws GeneralException, UnauthorizedException, NotFoundException {
+ String responseFixture = Resources.readResourceText("/fixtures/outbound_sms_prices.json");
+
+ MessageBirdService messageBirdService = SpyService
+ .expects("GET", "pricing/sms/outbound/smpp/test-smpp-user")
+ .withRestAPIBaseURL()
+ .andReturns(new APIResponse(responseFixture, 200));
+ MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService);
+
+ assertReceivedExpectedResponse(messageBirdClient.getOutboundSmsPrices("test-smpp-user"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetOutboundSmsPricesSmppUsernameNull() throws GeneralException, UnauthorizedException, NotFoundException {
+ new MessageBirdClient(mock(MessageBirdService.class)).getOutboundSmsPrices(null);
+ }
+
+ private static void assertReceivedExpectedResponse(OutboundSmsPriceResponse outboundSmsPriceResponse) {
+ assertEquals(10, outboundSmsPriceResponse.getGateway());
+ assertEquals("EUR", outboundSmsPriceResponse.getCurrencyCode());
+ assertEquals(3, outboundSmsPriceResponse.getTotalCount());
+
+ assertEquals(3, outboundSmsPriceResponse.getPrices().size());
+
+ assertEquals(new BigDecimal("0.060000"), outboundSmsPriceResponse.getPrices().get(0).getPrice());
+ assertEquals("EUR", outboundSmsPriceResponse.getPrices().get(0).getCurrencyCode());
+ assertEquals("0", outboundSmsPriceResponse.getPrices().get(0).getMccmnc());
+ assertEquals("0", outboundSmsPriceResponse.getPrices().get(0).getMcc());
+ assertNull(outboundSmsPriceResponse.getPrices().get(0).getMnc());
+ assertEquals("Default Rate", outboundSmsPriceResponse.getPrices().get(0).getCountryName());
+ assertEquals("XX", outboundSmsPriceResponse.getPrices().get(0).getCountryIsoCode());
+ assertEquals("Default Rate", outboundSmsPriceResponse.getPrices().get(0).getOperatorName());
+
+ assertEquals(new BigDecimal("0.047000"), outboundSmsPriceResponse.getPrices().get(1).getPrice());
+ assertEquals("EUR", outboundSmsPriceResponse.getPrices().get(1).getCurrencyCode());
+ assertEquals("202", outboundSmsPriceResponse.getPrices().get(1).getMccmnc());
+ assertEquals("202", outboundSmsPriceResponse.getPrices().get(1).getMcc());
+ assertNull(outboundSmsPriceResponse.getPrices().get(1).getMnc());
+ assertEquals("Greece", outboundSmsPriceResponse.getPrices().get(1).getCountryName());
+ assertEquals("GR", outboundSmsPriceResponse.getPrices().get(1).getCountryIsoCode());
+ assertNull(outboundSmsPriceResponse.getPrices().get(1).getOperatorName());
+
+ assertEquals(new BigDecimal("0.045000"), outboundSmsPriceResponse.getPrices().get(2).getPrice());
+ assertEquals("EUR", outboundSmsPriceResponse.getPrices().get(2).getCurrencyCode());
+ assertEquals("20205", outboundSmsPriceResponse.getPrices().get(2).getMccmnc());
+ assertEquals("202", outboundSmsPriceResponse.getPrices().get(2).getMcc());
+ assertEquals("05", outboundSmsPriceResponse.getPrices().get(2).getMnc());
+ assertEquals("Greece", outboundSmsPriceResponse.getPrices().get(2).getCountryName());
+ assertEquals("GR", outboundSmsPriceResponse.getPrices().get(2).getCountryIsoCode());
+ assertEquals("Vodafone", outboundSmsPriceResponse.getPrices().get(2).getOperatorName());
+ }
+}
diff --git a/api/src/test/java/com/messagebird/PurchasedNumbersFilterTest.java b/api/src/test/java/com/messagebird/PurchasedNumbersFilterTest.java
new file mode 100644
index 00000000..0a70c9ba
--- /dev/null
+++ b/api/src/test/java/com/messagebird/PurchasedNumbersFilterTest.java
@@ -0,0 +1,211 @@
+package com.messagebird;
+
+import com.messagebird.exceptions.GeneralException;
+import com.messagebird.objects.*;
+import org.junit.Test;
+
+import java.util.*;
+
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertNull;
+
+public class PurchasedNumbersFilterTest {
+
+ @Test
+ public void testDefaults() {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ // Test
+ assertEquals(10, filter.getLimit());
+ assertEquals(0, filter.getOffset());
+ assertEquals(0, filter.getFeatures().size());
+ assertEquals(0, filter.getTags().size());
+ assertNull(filter.getNumber());
+ assertNull(filter.getRegion());
+ assertNull(filter.getLocality());
+ assertNull(filter.getType());
+ }
+
+ @Test
+ public void testAddingFeatures() {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ assertEquals(EnumSet.noneOf(PhoneNumberFeature.class), filter.getFeatures());
+
+ // Test can add a feature
+ filter.addFeature(PhoneNumberFeature.SMS);
+ assertEquals(EnumSet.of(PhoneNumberFeature.SMS), filter.getFeatures());
+
+ // Test can have multiple features
+ filter.addFeature(PhoneNumberFeature.VOICE);
+ assertEquals(EnumSet.of(PhoneNumberFeature.SMS, PhoneNumberFeature.VOICE), filter.getFeatures());
+
+ // Test shouldn't have more than one of each
+ filter.addFeature(PhoneNumberFeature.SMS);
+ assertEquals(EnumSet.of(PhoneNumberFeature.SMS, PhoneNumberFeature.VOICE), filter.getFeatures());
+
+ filter.addFeature(PhoneNumberFeature.MMS);
+ assertEquals(EnumSet.of(PhoneNumberFeature.SMS, PhoneNumberFeature.VOICE, PhoneNumberFeature.MMS), filter.getFeatures());
+ }
+
+ @Test
+ public void testAddingMultipleFeatures() {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ assertEquals(EnumSet.noneOf(PhoneNumberFeature.class), filter.getFeatures());
+
+ // Test
+ filter.addFeature(PhoneNumberFeature.SMS, PhoneNumberFeature.VOICE);
+ assertEquals(EnumSet.of(PhoneNumberFeature.SMS, PhoneNumberFeature.VOICE), filter.getFeatures());
+ }
+
+ @Test
+ public void testRemovingFeatures() {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ // Test
+ filter.addFeature(PhoneNumberFeature.SMS, PhoneNumberFeature.MMS, PhoneNumberFeature.VOICE);
+ assertEquals(EnumSet.of(PhoneNumberFeature.SMS, PhoneNumberFeature.VOICE, PhoneNumberFeature.MMS), filter.getFeatures());
+
+ filter.removeFeature(PhoneNumberFeature.VOICE);
+ assertEquals(EnumSet.of(PhoneNumberFeature.SMS, PhoneNumberFeature.MMS), filter.getFeatures());
+
+ filter.removeFeature(PhoneNumberFeature.VOICE);
+ assertEquals(EnumSet.of(PhoneNumberFeature.SMS, PhoneNumberFeature.MMS), filter.getFeatures());
+
+ filter.removeFeature(PhoneNumberFeature.SMS, PhoneNumberFeature.MMS);
+ assertEquals(EnumSet.noneOf(PhoneNumberFeature.class), filter.getFeatures());
+ }
+
+ @Test
+ public void testAddingTags() {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ assertArrayEquals(new String[]{}, filter.getTags().toArray());
+
+ // Test
+ filter.addTag("TEST_TAG");
+ assertArrayEquals(new String[]{"TEST_TAG"}, filter.getTags().toArray());
+
+ filter.addTag("Another test tag");
+ assertArrayEquals(new String[]{"TEST_TAG", "Another test tag"}, filter.getTags().toArray());
+
+ filter.addTag("a", "b", "c");
+ assertArrayEquals(new String[]{"TEST_TAG", "Another test tag", "a", "b", "c"}, filter.getTags().toArray());
+ }
+
+ @Test
+ public void testAddingDuplicateTags() {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ filter.addTag("a");
+ filter.addTag("a");
+ filter.addTag("a", "b", "b", "c", "b");
+
+ assertArrayEquals(new String[]{"a", "b", "c"}, filter.getTags().toArray());
+ }
+
+ @Test
+ public void testRemoveTags() {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ filter.addTag("a", "b", "c", "d");
+ assertArrayEquals(new String[]{"a", "b", "c", "d"}, filter.getTags().toArray());
+
+ // Test
+ filter.removeTag("b");
+ assertArrayEquals(new String[]{"a", "c", "d"}, filter.getTags().toArray());
+
+ filter.removeTag("d");
+ assertArrayEquals(new String[]{"a", "c"}, filter.getTags().toArray());
+
+ filter.removeTag("b", "c", "d");
+ assertArrayEquals(new String[]{"a"}, filter.getTags().toArray());
+ }
+
+ @Test
+ public void testClearTags() {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ filter.addTag("a", "b", "c", "d");
+ assertArrayEquals(new String[]{"a", "b", "c", "d"}, filter.getTags().toArray());
+
+ // Test
+ filter.clearTags();
+ assertArrayEquals(new String[]{}, filter.getTags().toArray());
+ }
+
+ @Test
+ public void testBasicSetters() {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ assertNull(filter.getNumber());
+ assertNull(filter.getRegion());
+ assertNull(filter.getLocality());
+ assertNull(filter.getType());
+
+ // Test
+ filter.setLimit(23);
+ filter.setOffset(5);
+ filter.setNumber("TEST_NUMBER");
+ filter.setRegion("TEST_REGION");
+ filter.setLocality("TEST_LOCALITY");
+ filter.setType(PhoneNumberType.MOBILE);
+
+ assertEquals(23, filter.getLimit());
+ assertEquals(5, filter.getOffset());
+ assertEquals("TEST_NUMBER", filter.getNumber());
+ assertEquals("TEST_REGION", filter.getRegion());
+ assertEquals("TEST_LOCALITY", filter.getLocality());
+ assertEquals(PhoneNumberType.MOBILE, filter.getType());
+ }
+
+ @Test
+ public void testToHashMapWithDefaultValues() throws GeneralException {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ HashMap map = filter.toHashMap();
+
+ assertEquals(10, map.get("limit"));
+ assertEquals(0, map.get("offset"));
+ assertEquals(EnumSet.noneOf(PhoneNumberFeature.class), map.get("features"));
+ assertEquals(new ArrayList(), map.get("tags"));
+ }
+
+ @Test
+ public void testToHashMapWithAllValues() throws GeneralException {
+ // Setup
+ PurchasedNumbersFilter filter = new PurchasedNumbersFilter();
+
+ filter.setLimit(42);
+ filter.setOffset(24);
+ filter.setNumber("1234567890");
+ filter.setRegion("My Region");
+ filter.setLocality("My Locality");
+ filter.setType(PhoneNumberType.MOBILE);
+ filter.addFeature(PhoneNumberFeature.SMS, PhoneNumberFeature.VOICE);
+ filter.addTag("h", "e", "l", "l", "o");
+
+ HashMap map = filter.toHashMap();
+
+ assertEquals(42, map.get("limit"));
+ assertEquals(24, map.get("offset"));
+ assertEquals("1234567890", map.get("number"));
+ assertEquals("My Region", map.get("region"));
+ assertEquals("My Locality", map.get("locality"));
+ assertEquals("mobile", map.get("type").toString());
+ assertArrayEquals(new PhoneNumberFeature[]{PhoneNumberFeature.SMS, PhoneNumberFeature.VOICE}, ((Collection