public interface SmsNotification {
public void notifyUser
(String type
);
}
//TODO add telephone number field to entity and web form
/**
* Service class which gives opportunity send sms notifications to user's mobile phone,
* get balance on service account, get status of notification (in this partial case vendor is)
*/
@Service
public class SmsNotificationTurboSmsImpl implements SmsNotification {
private final static Logger logger = LoggerFactory.getLogger(SmsNotificationTurboSmsImpl.class);
private final static String NEW_MESSAGE_TYPE
= "message"; private final static String NEW_VIDEO_TYPE
= "video"; private final static String NEW_ADVERTISEMENT_TYPE
= "advertisement";
@Inject
private UserRepository userRepository;
@Inject
private static MessageSource messageSource;
@Inject
private RequestBuilder requestBuilder;
private boolean serviceEnabled;
private final static JHipsterProperties jHipsterProperties = new JHipsterProperties();
private final static String serviceLogin
= jHipsterProperties.
getSmsNotification().
getLogin(); private final static String servicePassword
= jHipsterProperties.
getSmsNotification().
getPassword(); private final static String applicationName
= jHipsterProperties.
getSmsNotification().
getApplicationName();
private final static String WRONG_MESSAGE_TYPE
= "Wrong message type";
/*
* Top level method to notify user by sending him sms
* Invoking service classes to get xml query, establish connection,
* send request and process response in well-read form
* Result should be logged for further control by administrator
* Current user getting from security utils class
* @param type - type of message(new message or video etc)
*
*/
@Override
public void notifyUser
(String type
) { if(serviceEnabled = false){
return;
}
String login
= SecurityUtils.
getCurrentUserLogin(); User user = userRepository.findOneByLogin(login).get();
logger.debug("Invoke notifyUser method with user {}, message type {}", user.getLogin(), type);
String telNumber
= user.
getEmail(); //TODO add new field to user entity! String request
= getRequestStringToSendSms
(type, locale, telNumber
); logger.info("Sending sms notification request was sent to service, message recipient is user {}, message type is {}", user.getLogin(), type);
String rawResponse
= requestBuilder.
doXMLQuery(request
); String parsedResponse
= SmsNotificationUtils.
parseDeliveryOrBalanceReport(rawResponse
); logger.info("Notification service response after sending request to notify user {} with message type {} is: {}", user.getLogin(), type, parsedResponse);
}
/*
* Top-level method for getting current account balance of sms service
* @return parsed response - response in well-read form
*/
@Override
String request
= getRequestStringToGetBalance
(); String rawResponse
= requestBuilder.
doXMLQuery(request
); String parsedResponse
= SmsNotificationUtils.
parseDeliveryOrBalanceReport(rawResponse
); return parsedResponse;
}
/*
* Top-level method for getting status of particular sms
* @param msgId - sms id
* @return response in well-read form
*/
@Override
String request
= getRequestStringToGetSmsStatus
(msgId
); String rawResponse
= requestBuilder.
doXMLQuery(request
); String parsedResponse
= SmsNotificationUtils.
parseMessageStatusResponse(rawResponse
); return parsedResponse;
}
/*
* Create a string with XML query for sending sms
* @param type - type of message
* @param locale - user's locale
* @param telNumber
* @return string with query
*/
switch (type){
case NEW_ADVERTISEMENT_TYPE : text = messageSource.getMessage("sms.notification.newAdvertisement", null, locale);
case NEW_MESSAGE_TYPE : text = messageSource.getMessage("sms.notification.newMessage", null, locale);
case NEW_VIDEO_TYPE : text = messageSource.getMessage("sms.notification.newVideo", null, locale);
}
if(text.equals(null)){
}
StringBuilder sb = new StringBuilder();
sb.append("<SMS>");
sb.append("<operations>");
sb.append("<operation>SEND</operation>");
sb.append("</operations>");
sb.append("<authentification>");
sb.append("<username>" + serviceLogin + "</username>");
sb.append("<password>" + servicePassword + "</password>");
sb.append("</authentification>");
sb.append("<message>");
sb.append("<sender>" + applicationName + "</sender>");
sb.append("<text>" + text + "</text>");
sb.append("</message>");
sb.append("<numbers>");
sb.append(telNumber);
sb.append("</numbers>");
sb.append("</SMS>");
return sb.toString();
}
/*
* Creates a string with XML query for getting account balance
*/
private static String getRequestStringToGetBalance
(){ StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
stringBuilder.append("<SMS>");
stringBuilder.append("<operations>");
stringBuilder.append("<operation>BALANCE</operation>");
stringBuilder.append("</operations>");
stringBuilder.append("<authentification>");
stringBuilder.append("<username>" + serviceLogin + "</username>");
stringBuilder.append("<password>" + servicePassword + "</password>");
stringBuilder.append("</authentification> ");
stringBuilder.append("</SMS>");
return stringBuilder.toString();
}
/*
* Creates a string with XML query for getting message status details
* @param msgId - sms id
* @return string with query
*/
private static String getRequestStringToGetSmsStatus
(String msgId
){ StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
stringBuilder.append("<SMS><operations><operation>GETSTATUS</operation></operations>");
stringBuilder.append("<authentification>");
stringBuilder.append("<username>" + serviceLogin + "</username>");
stringBuilder.append("<password>" + servicePassword + "</password>");
stringBuilder.append("</authentification>");
stringBuilder.append("<statistics>");
stringBuilder.append("<messageid>" + msgId + "</messageid>");
stringBuilder.append("</statistics>");
stringBuilder.append("</SMS>");
return stringBuilder.toString();
}
public SmsNotificationTurboSmsImpl() {
}
public UserRepository getUserRepository() {
return userRepository;
}
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public static MessageSource getMessageSource() {
return messageSource;
}
public static void setMessageSource(MessageSource messageSource) {
SmsNotificationTurboSmsImpl.messageSource = messageSource;
}
public RequestBuilder getRequestBuilder() {
return requestBuilder;
}
public void setRequestBuilder(RequestBuilder requestBuilder) {
this.requestBuilder = requestBuilder;
}
public boolean isServiceEnabled() {
return serviceEnabled;
}
public void setServiceEnabled(boolean serviceEnabled) {
this.serviceEnabled = serviceEnabled;
}
public static JHipsterProperties getjHipsterProperties() {
return jHipsterProperties;
}
}
/**
* Connection with sms service API
*/
public class TurboSmsVendorConnection {
private final static Logger logger = LoggerFactory.getLogger(TurboSmsVendorConnection.class);
private final static Boolean USE_CACHES
= false; private final static Boolean DO_INPUT
= true; private final static Boolean DO_OUTPUT
= true; private final static String CONNECTION_ERROR
= "Connection is not established!"; private final static String ENCODING
= "UTF-8"; private final static String PARAM_SEPARATOR
= "&"; private final static String PARAM_EQUAL
= "=";
/*
* Makes HTTP request using POST method to specified URL
* @param requestURL - message service URL
* @param params - map contains POST data in pairs key-value
* @throws IOException
* @return httpURLConnection instance
*/
logger.debug("Invoke sendPostRequest method");
URL url
= new URL(requestUrl
); httpURLConnection.setUseCaches(USE_CACHES);
httpURLConnection.setDoInput(DO_INPUT);
if (params != null && params.size() > 0) {
httpURLConnection.setDoOutput(DO_OUTPUT); // true indicates POST request
Iterator<String> paramIterator = params.keySet().iterator();
boolean isFirst=true;
while (paramIterator.hasNext()) {
String key
= paramIterator.
next(); String value
= params.
get(key
); if(!isFirst){ requestParams.append(PARAM_SEPARATOR);}
requestParams.append(key);
requestParams.
append(PARAM_EQUAL
).
append(URLEncoder.
encode(value, ENCODING
)); isFirst=false;
}
// sends POST request
URLEncoder.
encode(requestParams.
toString(), ENCODING
); writer.write(requestParams.toString());
writer.flush();
}
return httpURLConnection;
}
/*
* Pulls string with response from httpURLConnection
* @throws IOException
*/
logger.debug("Invoke readSingleLine method.");
if(httpURLConnection != null){
inputStream = httpURLConnection.getInputStream();
}else {
logger.error("HttpURLConnection not established.");
}
String response
= bufferedReader.
readLine(); bufferedReader.close();
return response;
}
/*
* Pulls multiple strings from httpURLConnection
* @return divided by lines response in a string array
* @throws IOException
*/
logger.debug("Invoke readMultipleLinesResponse method.");
if (httpURLConnection != null) {
inputStream = httpURLConnection.getInputStream();
} else {
logger.error("HttpURLConnection is not established.");
}
List<String> response = new ArrayList<String>();
while ((line = reader.readLine()) != null) {
response.add(line);
}
reader.close();
}
/*
* Disconnect method
*/
public static void disconnect() {
if(httpURLConnection != null){
httpURLConnection.disconnect();
}
}
return httpURLConnection;
}
TurboSmsVendorConnection.httpURLConnection = httpURLConnection;
}
}
/**
* Wrapper class to perform XML request and getting response
*/
public class RequestBuilder {
private final static Logger logger = LoggerFactory.getLogger(RequestBuilder.class);
private final static String CONNECTION_PARAM
= "XML";
@Inject
private JHipsterProperties jHipsterProperties;
/*
* This method getting string with string query in XML format,
* invoking connection and returning result string
* @throws IOException in case appearing any IO exception
* @param xml - string with query in XML format
* @return string with service response
*/
logger.debug("Invoke doXMLQuery method.");
StringBuilder responseString = new StringBuilder();
params.put(CONNECTION_PARAM, xml);
try {
TurboSmsVendorConnection.sendPostRequest(jHipsterProperties.getSmsNotification().getURL(), params);
String[] response
= TurboSmsVendorConnection.
readMultipleLinesResponse(); for (String line
: response
) { responseString.append(line);
}
logger.error("IOException in doXMLQueryMethod.", e);
}
TurboSmsVendorConnection.disconnect();
return responseString.toString();
}
public JHipsterProperties getjHipsterProperties() {
return jHipsterProperties;
}
public void setjHipsterProperties(JHipsterProperties jHipsterProperties) {
this.jHipsterProperties = jHipsterProperties;
}
}
/**
* Utilize class for processing request "raw" string to well-read form
*/
public final class SmsNotificationUtils {
private final static String STATUS_PREFIX
= "<status>"; private final static String STATUS_SUFFIX
= "</status>";
private final static String CREDITS_PREFIX
= "<credits>"; private final static String CREDITS_SUFFIX
= "</credits>";
private final static String SENT_DATE
= "sentdate="; private final static String DONE_DATE
= "donedate="; private final static String STATUS
= "status=";
private static final Map
<String, String
> deliveryMessageStatusCodes
= new HashMap
<>(); static {
deliveryMessageStatusCodes.put("-1", "Authentication failed.");
deliveryMessageStatusCodes.put("-2", "Wrong XML syntax.");
deliveryMessageStatusCodes.put("-3", "Not enough credits on account balance.");
deliveryMessageStatusCodes.put("1", "Message was delivered successfully.");
deliveryMessageStatusCodes.put("0", "Request balance status is OK");
}
private static final Map
<String, String
> messageStatuses
= new HashMap
<>(); {
messageStatuses.put("SENT", "Message was sent.");
messageStatuses.put("NOT_DELIVERED", "Message wasn't delivered.");
messageStatuses.put("DELIVERED", "Message was delivered");
messageStatuses.put("INVALID_DESTINATION_ADDRESS", "Destination address invalid.");
messageStatuses.put("INVALID_SOURCE_ADDRESS", "Source address invalid");
messageStatuses.put("NOT_ALLOWED", "Recipient mobile operator is out of processing.");
messageStatuses.put("NOT_ENOUGH_CREDITS", "Not enough credits on account balance.");
}
private final static String DELIVERY_STATUS
= "Delivery status is: "; private final static String AVAILABLE_CREDITS
= "Available credits: ";
private final static String SEND_DATE_TEXT
= "Message send date: "; private final static String DONE_DATE_TEXT
= "Message delivered at: "; private final static String STATUS_TEXT
= "Delivery status is: ";
/*
* Convert raw response with number codes after string after message sending to well-read form
* @param serverResponse - raw string from server
* @return string in well-read form
*/
public static String parseDeliveryOrBalanceReport
(String serverResponse
){ //typical service response is: <?xml version="1.0" encoding="UTF-8"?><RESPONSE><status>2</status><credits>0.682</credits></RESPONSE>
//AUTH_FAILED -1 Wrong login or password
//XML_ERROR -2 Wrong XML syntax
//NOT_ENOUGH_CREDITS -3 Not enough credits on account balance
//NO_RECIPIENTS -4 Missing correct recipient number
//SEND_OK >0 count of received messages, in this case should always be 1
//SEND_OK 0 request to get current credit balance on service account
String statusCode
= serverResponse.
substring(serverResponse.
indexOf(STATUS_PREFIX
) + STATUS_PREFIX.
length(),
serverResponse.indexOf(STATUS_SUFFIX));
String availableCredits
= serverResponse.
substring(serverResponse.
indexOf(CREDITS_PREFIX
) + CREDITS_PREFIX.
length(),
serverResponse.indexOf(CREDITS_SUFFIX));
String convertedStatusForm
= deliveryMessageStatusCodes.
get(statusCode
); return DELIVERY_STATUS + convertedStatusForm + AVAILABLE_CREDITS + availableCredits;
}
/*
* Parses string with "raw" response to well-read form
* @param messageStatusResponse - response string
*/
public static String parseMessageStatusResponse
(String messageStatusResponse
){ //typical service response is: <?xml version="1.0" encoding="UTF-8"?><deliveryreport><message id="1299" sentdate="0000-00-00 00:00:00" donedate="0000-00-00 00:00:00" status="0" /></deliveryreport>
//SENT Was sent
//NOT_DELIVERED Wasn't delivered
//DELIVERED Delivered
//NOT_ALLOWED Operator is not allowed
//INVALID_DESTINATION_ADDRESS Wrong destination address
//INVALID_SOURCE_ADDRESS Wrong source name
//NOT_ENOUGH_CREDITS Not enough credits
String send_date
= messageStatusResponse.
substring(messageStatusResponse.
indexOf(SENT_DATE
) + SENT_DATE.
length(),
messageStatusResponse.indexOf(SENT_DATE) + SENT_DATE.length() + 21);
String done_date
= messageStatusResponse.
substring(messageStatusResponse.
indexOf(DONE_DATE
) + DONE_DATE.
length(),
messageStatusResponse.indexOf(DONE_DATE) + DONE_DATE.length() + 21);
String status_code
= messageStatusResponse.
substring(messageStatusResponse.
indexOf(STATUS
) + STATUS.
length(),
messageStatusResponse.lastIndexOf("\""));
String status
= messageStatuses.
get(status_code
); return SEND_DATE_TEXT + send_date + DONE_DATE_TEXT + done_date + STATUS_TEXT + status;
}
}
public class SmsNotificationTest {
@Mock
private RequestBuilder requestBuilder;
@Inject
TurboSmsVendorConnection turboSmsVendorConnection;
SmsNotificationTurboSmsImpl turboSms;
@Before
public void setup(){
MockitoAnnotations.initMocks(this);
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin"));
SecurityContextHolder.setContext(securityContext);
turboSms = new SmsNotificationTurboSmsImpl();
when(requestBuilder.doXMLQuery(anyString())).thenReturn("<?xml version=\"1.0\" encoding=\"UTF-8\"?><RESPONSE><status>1</status><credits>0.682</credits></RESPONSE>");
turboSms.setServiceEnabled(true);
ReflectionTestUtils.setField(turboSms, "requestBuilder", requestBuilder);
}
@Test
public void testDeliveryOrBalanceReport(){
assertThat(turboSms.notifyUser("message")).isEqualTo("Delivery status is: Message was delivered successfully.Available credits: 0.628");
}
}